From df2f260509a0a5a77b9818c39ca35f656dbd38b2 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 21 Feb 2018 10:44:20 +0100 Subject: [PATCH 001/773] 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/773] 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/773] [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/773] 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/773] 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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] [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/773] 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/773] 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/773] [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/773] [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/773] 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/773] 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/773] 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/773] 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/773] [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/773] 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/773] 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/773] 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/773] 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/773] 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/773] 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/773] 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/773] 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/773] 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/773] 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 1bcee031826d8ac6b4ded2c30788cba8b369cb30 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Sun, 21 Aug 2022 23:01:27 +0800 Subject: [PATCH 042/773] xrootd: fix missing byteswap.h error on MacOS build with gcc --- src/XrdOssCsi/XrdOssCsiTagstoreFile.hh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh b/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh index 9f0a8721f4e..b1a94acd668 100644 --- a/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh +++ b/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh @@ -37,7 +37,19 @@ #include #include + +#if defined(__GLIBC__) || defined(__BIONIC__) || defined(__CYGWIN__) #include +#elif 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 +#endif class XrdOssCsiTagstoreFile : public XrdOssCsiTagstore { From f8f127d2672cc388fe336496748c290f1dbbf35b Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sun, 28 Aug 2022 12:06:59 +0200 Subject: [PATCH 043/773] Address some warnings from Doxygen --- src/Xrd/XrdLink.hh | 16 ++- src/Xrd/XrdLinkCtl.hh | 10 +- src/Xrd/XrdTcpMonPin.hh | 2 + .../XrdClRecordPlugin/XrdClRecorder.hh | 2 +- src/XrdCks/XrdCksLoader.hh | 2 +- src/XrdCks/XrdCksWrapper.hh | 2 +- src/XrdCl/XrdClArg.hh | 8 +- src/XrdCl/XrdClAsyncHSWriter.hh | 1 - src/XrdCl/XrdClAsyncPageReader.hh | 6 +- src/XrdCl/XrdClChannel.hh | 7 +- src/XrdCl/XrdClFile.hh | 5 +- src/XrdCl/XrdClFileOperations.hh | 8 +- src/XrdCl/XrdClFileStateHandler.hh | 1 - src/XrdCl/XrdClLocalFileHandler.hh | 8 +- src/XrdCl/XrdClLog.hh | 2 +- src/XrdCl/XrdClMetalinkRedirector.hh | 2 +- src/XrdCl/XrdClOperationHandlers.hh | 12 +-- src/XrdCl/XrdClOperations.hh | 16 +-- src/XrdCl/XrdClPostMasterInterfaces.hh | 3 +- src/XrdCl/XrdClSocket.hh | 11 +- src/XrdCl/XrdClXCpCtx.hh | 2 +- src/XrdCl/XrdClXCpSrc.hh | 2 +- src/XrdCl/XrdClXRootDMsgHandler.hh | 9 +- src/XrdCl/XrdClZipArchive.hh | 1 - src/XrdCl/XrdClZipOperations.hh | 2 +- src/XrdCms/XrdCmsUtils.hh | 2 +- src/XrdEc/XrdEcRedundancyProvider.hh | 8 +- src/XrdNet/XrdNetCache.hh | 2 +- src/XrdNet/XrdNetIF.hh | 4 +- src/XrdNet/XrdNetMsg.hh | 6 +- src/XrdNet/XrdNetUtils.hh | 15 ++- src/XrdOfs/XrdOfsCPFile.hh | 4 +- src/XrdOfs/XrdOfsConfigPI.hh | 2 +- src/XrdOfs/XrdOfsHandle.cc | 2 +- src/XrdOfs/XrdOfsPrepare.hh | 2 +- src/XrdOss/XrdOss.hh | 26 ++--- src/XrdOss/XrdOssAt.hh | 2 +- src/XrdOss/XrdOssStatInfo.hh | 56 +++++----- src/XrdOss/XrdOssWrapper.hh | 21 ++-- src/XrdOuc/XrdOucBackTrace.hh | 2 +- src/XrdOuc/XrdOucBuffer.hh | 2 +- src/XrdOuc/XrdOucCRC.hh | 4 +- src/XrdOuc/XrdOucCache.hh | 24 +++-- src/XrdOuc/XrdOucERoute.hh | 2 +- src/XrdOuc/XrdOucErrInfo.hh | 2 - src/XrdOuc/XrdOucFileInfo.hh | 2 +- src/XrdOuc/XrdOucGMap.hh | 10 +- src/XrdOuc/XrdOucGatherConf.hh | 2 +- src/XrdOuc/XrdOucPgrwUtils.hh | 4 +- src/XrdOuc/XrdOucPinKing.hh | 8 +- src/XrdOuc/XrdOucVerName.hh | 2 +- src/XrdPosix/XrdPosixCache.hh | 6 +- src/XrdPosix/XrdPosixXrootd.hh | 3 - src/XrdPss/XrdPss.hh | 2 +- src/XrdRmc/XrdRmc.hh | 100 +++++++++--------- src/XrdSec/XrdSecInterface.hh | 14 +-- src/XrdSec/XrdSecProtect.hh | 4 +- src/XrdSecsss/XrdSecsssID.hh | 6 +- src/XrdSfs/XrdSfsGPFile.hh | 4 +- src/XrdSfs/XrdSfsInterface.hh | 48 ++++----- src/XrdSsi/XrdSsiLogger.hh | 4 +- src/XrdSsi/XrdSsiRequest.hh | 4 +- src/XrdSsi/XrdSsiResponder.hh | 2 +- src/XrdSsi/XrdSsiService.hh | 8 +- src/XrdSsi/XrdSsiShMap.hh | 4 +- src/XrdSys/XrdSysLogPI.hh | 2 +- src/XrdSys/XrdSysLogger.hh | 2 +- src/XrdSys/XrdSysXAttr.hh | 2 +- src/XrdTls/XrdTls.hh | 4 +- src/XrdTls/XrdTlsSocket.hh | 2 +- src/XrdXrootd/XrdXrootdBridge.hh | 12 +-- src/XrdXrootd/XrdXrootdGPFile.hh | 2 +- 72 files changed, 288 insertions(+), 303 deletions(-) diff --git a/src/Xrd/XrdLink.hh b/src/Xrd/XrdLink.hh index 0b167fa3003..3d11e9d6832 100644 --- a/src/Xrd/XrdLink.hh +++ b/src/Xrd/XrdLink.hh @@ -153,7 +153,7 @@ static XrdLink *Find(int &curr, XrdLinkMatch *who=0); //----------------------------------------------------------------------------- //! Find the next client name matching certain attributes. //! -//! @param cur Is an internal tracking value that allows repeated calls. +//! @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 zero is returned. //! @param bname Pointer to a buffer where the name is to be returned. @@ -306,7 +306,8 @@ int Recv(char *buff, int blen, int timeout); //! //! @param iov pointer to the message vector. //! @param iocnt number of iov elements in the vector. -//! @param bytes the sum of the sizes in the vector. +//! @param timeout milliseconds to wait for data. A negative value waits +//! forever. //! //! @return >=0 number of bytes read. //! < 0 an error occurred or when -ETIMEDOUT is returned, no data @@ -336,9 +337,9 @@ int RecvAll(char *buff, int blen, int timeout=-1); //------------------------------------------------------------------------------ //! Register a host name with this IP address. This is not MT-safe! //! -//! @param hName -> to a true host name which should be fully qualified. -//! One of the IP addresses registered to this name must -//! match the IP address associated with this object. +//! @param hName pointer to a true host name which should be fully qualified. +//! One of the IP addresses registered to this name must +//! match the IP address associated with this object. //! //! @return True: Specified name is now associated with this link. //! False: Nothing changed, registration could not be verified. @@ -479,7 +480,7 @@ bool setTLS(bool enable, XrdTlsContext *ctx=0); //----------------------------------------------------------------------------- //! Shutdown the link but otherwise keep it intact. //! -//! @param getlock if true, the operation is performed under a lock. +//! @param getLock if true, the operation is performed under a lock. //----------------------------------------------------------------------------- void Shutdown(bool getLock); @@ -555,9 +556,6 @@ bool hasBridge() const {return isBridged;} //----------------------------------------------------------------------------- //! Determine if this link is using TLS. //! -//! @param vprot if not nil, the TLS protocol version number if returned. -//! If the link is not using TLS the version is a null string. -//! //! @return true this link is using TLS. //! @return false this link not using TLS. //----------------------------------------------------------------------------- diff --git a/src/Xrd/XrdLinkCtl.hh b/src/Xrd/XrdLinkCtl.hh index 9a20aca8f56..1fee8ac5d92 100644 --- a/src/Xrd/XrdLinkCtl.hh +++ b/src/Xrd/XrdLinkCtl.hh @@ -110,7 +110,7 @@ static XrdPollInfo *fd2PollInfo(int fd) //----------------------------------------------------------------------------- //! Find the next link matching certain attributes. //! -//! @param cur Is an internal tracking value that allows repeated calls. +//! @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 @@ -128,7 +128,7 @@ static XrdLink *Find(int &curr, XrdLinkMatch *who=0); //----------------------------------------------------------------------------- //! Find the next client name matching certain attributes. //! -//! @param cur Is an internal tracking value that allows repeated calls. +//! @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 zero is returned. //! @param bname Pointer to a buffer where the name is to be returned. @@ -152,8 +152,8 @@ static void idleScan(); //----------------------------------------------------------------------------- //! Set kill constants. //! -//! @param wksec Seconds to wait for kill to happed, -//! @param kwsec The minimum number of seconds to wait after killing a +//! @param wkSec Seconds to wait for kill to happed, +//! @param kwSec The minimum number of seconds to wait after killing a //! connection for it to end. //----------------------------------------------------------------------------- @@ -162,7 +162,7 @@ static void setKWT(int wkSec, int kwSec); //----------------------------------------------------------------------------- //! Setup link processing. //! -//! @param maaxfds The maximum number of connections to handle. +//! @param maxfds The maximum number of connections to handle. //! @param idlewt The time interval to check for idle connections. //! //! @return !0 Successful. diff --git a/src/Xrd/XrdTcpMonPin.hh b/src/Xrd/XrdTcpMonPin.hh index 14bc21660be..bdaa83b1cb7 100644 --- a/src/Xrd/XrdTcpMonPin.hh +++ b/src/Xrd/XrdTcpMonPin.hh @@ -37,7 +37,9 @@ object exists in the environment. You get the g-stream object specific to this plugin by executing the following (assume envR is the environment): + @code {.cpp} XrdXrootdGStream *gS = (XrdXrootdGStream *)envR.GetPtr("TcpMon.gStream*"); + @endcode */ class XrdTcpMonPin diff --git a/src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh b/src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh index 390d15b4423..c9eb2052b94 100644 --- a/src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh +++ b/src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh @@ -221,7 +221,7 @@ public: //---------------------------------------------------------------------------- //! Create the output csv file - //! @param path : path for the file to be created + //! @param cfgpath : path for the file to be created //---------------------------------------------------------------------------- inline static void SetOutput( const std::string &cfgpath ) { diff --git a/src/XrdCks/XrdCksLoader.hh b/src/XrdCks/XrdCksLoader.hh index 09e9bc2d13f..7293cb3d9cf 100644 --- a/src/XrdCks/XrdCksLoader.hh +++ b/src/XrdCks/XrdCksLoader.hh @@ -50,7 +50,7 @@ public: //! 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 csName 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 diff --git a/src/XrdCks/XrdCksWrapper.hh b/src/XrdCks/XrdCksWrapper.hh index 0fb38c66868..dc87f552c75 100644 --- a/src/XrdCks/XrdCksWrapper.hh +++ b/src/XrdCks/XrdCksWrapper.hh @@ -233,7 +233,7 @@ int Ver( const char *Xfn, XrdCksData &Cks, XrdCksPCB *pcbP) //! Constructor //! //! @param prevPI Reference to the antecedent plugin. -//! Wparam errP Pointer to error message object +//! @param errP Pointer to error message object //------------------------------------------------------------------------------ XrdCksWrapper(XrdCks &prevPI, XrdSysError *errP) diff --git a/src/XrdCl/XrdClArg.hh b/src/XrdCl/XrdClArg.hh index 49233f9eb84..3ff9fce9f5b 100644 --- a/src/XrdCl/XrdClArg.hh +++ b/src/XrdCl/XrdClArg.hh @@ -163,7 +163,7 @@ namespace XrdCl //-------------------------------------------------------------------- //! Constructor //! - //! @param value : the future value to be hold by us + //! @param ftr : the future value to be hold by us //-------------------------------------------------------------------- FutureValue( std::future &&ftr ) : ftr( std::move( ftr ) ) { @@ -195,7 +195,7 @@ namespace XrdCl //-------------------------------------------------------------------- //! Constructor //! - //! @param value : the forwarded value to be hold by us + //! @param fwd : the forwarded value to be hold by us //-------------------------------------------------------------------- FwdValue( const Fwd &fwd ) : fwd( fwd ) { @@ -305,7 +305,7 @@ namespace XrdCl //------------------------------------------------------------------------ //! Constructor. //! - //! @param value : value of the argument + //! @param str : value of the argument //------------------------------------------------------------------------ Arg( std::string str ) : ArgBase( str ) { @@ -314,7 +314,7 @@ namespace XrdCl //------------------------------------------------------------------------ //! Constructor. //! - //! @param val : value of the argument + //! @param cstr : value of the argument //------------------------------------------------------------------------ Arg( const char *cstr ) : ArgBase( cstr ) { diff --git a/src/XrdCl/XrdClAsyncHSWriter.hh b/src/XrdCl/XrdClAsyncHSWriter.hh index 4f036d8e23c..c1bd52d6390 100644 --- a/src/XrdCl/XrdClAsyncHSWriter.hh +++ b/src/XrdCl/XrdClAsyncHSWriter.hh @@ -41,7 +41,6 @@ namespace XrdCl //------------------------------------------------------------------------ //! Constructor //! - //! @param xrdTransport : the (xrootd) transport layer //! @param socket : the socket with the message to be read out //! @param strmname : stream name //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClAsyncPageReader.hh b/src/XrdCl/XrdClAsyncPageReader.hh index 5dc246a99ae..1a02b2cc00a 100644 --- a/src/XrdCl/XrdClAsyncPageReader.hh +++ b/src/XrdCl/XrdClAsyncPageReader.hh @@ -42,10 +42,7 @@ class AsyncPageReader //! Constructor //! //! @param chunks : list of buffer for the data - //! @param socket : the socket with the data //! @param digests : a vector that will be filled with crc32c digest data - //! @param dlen : total size of data (including crc32 digests) in the - //! server response //-------------------------------------------------------------------------- AsyncPageReader( ChunkList &chunks, std::vector &digests ) : @@ -100,7 +97,8 @@ class AsyncPageReader } //-------------------------------------------------------------------------- - //! Readout date from the socket + //! Readout data from the socket + //! @param socket : the socket with the data //! @param btsread : number of user data read from the socket //! @return : operation status //-------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClChannel.hh b/src/XrdCl/XrdClChannel.hh index 70c9cd80eeb..0bd9474822e 100644 --- a/src/XrdCl/XrdClChannel.hh +++ b/src/XrdCl/XrdClChannel.hh @@ -83,16 +83,15 @@ namespace XrdCl //! pushed through the wire or when the timeout elapses //! //! @param msg message to be sent - //! @apram stateful physical stream disconnection causes an error + //! @param handler handler to be notified about the status + //! @param 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 //------------------------------------------------------------------------ XRootDStatus Send( Message *msg, - MsgHandler *handler, + MsgHandler *handler, bool stateful, time_t expires ); diff --git a/src/XrdCl/XrdClFile.hh b/src/XrdCl/XrdClFile.hh index 4f1a0caf0e3..db9fbc0e4a2 100644 --- a/src/XrdCl/XrdClFile.hh +++ b/src/XrdCl/XrdClFile.hh @@ -218,8 +218,8 @@ namespace XrdCl //! @param offset offset from the beginning of the file //! @param size buffer size, at least 1 page big (4KB) //! @param buffer a pointer to a buffer big enough to hold the data - //! @param bytesRead number of bytes actually read //! @param cksums crc32c checksum for each read 4KB page + //! @param bytesRead number of bytes actually read //! @param timeout timeout value, if 0 the environment default will be //! used //! @return status of the operation @@ -525,7 +525,6 @@ namespace XrdCl //! @param offset offset from the beginning of the file //! @param iov list of the buffers to //! @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 @@ -558,7 +557,7 @@ namespace XrdCl //! @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 bytesRead number of bytes actually read //! @param timeout timeout value, if 0 then the environment default //! will be used //! @return status of the operation diff --git a/src/XrdCl/XrdClFileOperations.hh b/src/XrdCl/XrdClFileOperations.hh index 8252c4fbb65..ace6177945a 100644 --- a/src/XrdCl/XrdClFileOperations.hh +++ b/src/XrdCl/XrdClFileOperations.hh @@ -154,7 +154,7 @@ namespace XrdCl //! //! @arg from : state from which the object is being converted //! - //! @param op : the object that is being converted + //! @param open : the object that is being converted //------------------------------------------------------------------------ template OpenImpl( OpenImpl && open ) : @@ -173,7 +173,7 @@ namespace XrdCl //! Overload of operator>> defined in ConcreteOperation, we're adding //! additional capabilities by using ExResp factory (@see ExResp). //! - //! @param func : function/functor/lambda + //! @param hdlr : function/functor/lambda //------------------------------------------------------------------------ template OpenImpl operator>>( Hdlr &&hdlr ) @@ -195,8 +195,8 @@ namespace XrdCl //------------------------------------------------------------------------ //! RunImpl operation (@see Operation) //! - //! @param timeout : pipeline timeout - //! @return : status of the operation + //! @param pipelineTimeout : pipeline timeout + //! @return : status of the operation //------------------------------------------------------------------------ XRootDStatus RunImpl( PipelineHandler *handler, uint16_t pipelineTimeout ) { diff --git a/src/XrdCl/XrdClFileStateHandler.hh b/src/XrdCl/XrdClFileStateHandler.hh index 923198dbd61..197151321f8 100644 --- a/src/XrdCl/XrdClFileStateHandler.hh +++ b/src/XrdCl/XrdClFileStateHandler.hh @@ -331,7 +331,6 @@ namespace XrdCl //! @param offset offset from the beginning of the file //! @param size buffer size //! @param buffer a pointer to a buffer holding data pages - //! @param cksums the crc32c checksums for each 4KB page //! @param handler handler to be notified when the response arrives //! @param timeout timeout value, if 0 the environment default will be //! used diff --git a/src/XrdCl/XrdClLocalFileHandler.hh b/src/XrdCl/XrdClLocalFileHandler.hh index e04a6a7e09b..461433a9475 100644 --- a/src/XrdCl/XrdClLocalFileHandler.hh +++ b/src/XrdCl/XrdClLocalFileHandler.hh @@ -179,8 +179,7 @@ namespace XrdCl //! 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 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 @@ -292,10 +291,9 @@ namespace XrdCl uint16_t timeout = 0 ); //------------------------------------------------------------------------ - //! creates the directories specified in file_path + //! creates the directories specified in path //! - //! @param file_path specifies which directories are to be created - //! @param mode same access modes as for the desired file operation + //! @param path specifies which directories are to be created //! @return status of the mkdir system call //------------------------------------------------------------------------ static XRootDStatus MkdirPath( const std::string &path ); diff --git a/src/XrdCl/XrdClLog.hh b/src/XrdCl/XrdClLog.hh index 3b4b50a7760..bd06d427750 100644 --- a/src/XrdCl/XrdClLog.hh +++ b/src/XrdCl/XrdClLog.hh @@ -161,7 +161,7 @@ namespace XrdCl //! Always print the message //! //! @param level log level - //! @param type topic of the message + //! @param topic topic of the message //! @param format format string - the same as in printf //! @param list list of arguments //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClMetalinkRedirector.hh b/src/XrdCl/XrdClMetalinkRedirector.hh index 62fab2e808e..c8c31da79f8 100644 --- a/src/XrdCl/XrdClMetalinkRedirector.hh +++ b/src/XrdCl/XrdClMetalinkRedirector.hh @@ -37,7 +37,6 @@ class MetalinkRedirector : public VirtualRedirector //---------------------------------------------------------------------------- //! Constructor //! @param url : URL to the metalink file - //! @param userHandler : the response handler provided by end user //---------------------------------------------------------------------------- MetalinkRedirector( const std::string &url ); @@ -48,6 +47,7 @@ class MetalinkRedirector : public VirtualRedirector //---------------------------------------------------------------------------- //! Initializes the object with the content of the metalink file + //! @param userHandler : the response handler provided by end user //---------------------------------------------------------------------------- XRootDStatus Load( ResponseHandler *userHandler ); diff --git a/src/XrdCl/XrdClOperationHandlers.hh b/src/XrdCl/XrdClOperationHandlers.hh index 7af15cd808e..96af09fc966 100644 --- a/src/XrdCl/XrdClOperationHandlers.hh +++ b/src/XrdCl/XrdClOperationHandlers.hh @@ -620,8 +620,8 @@ namespace XrdCl //------------------------------------------------------------------------ //! A factory method, simply forwards the given handler //! - //! @param h : the ResponseHandler that should be wrapped - //! @return : a ForwardingHandler instance + //! @param hdlr : the ResponseHandler that should be wrapped + //! @return : a ForwardingHandler instance //------------------------------------------------------------------------ inline static ResponseHandler* Create( ResponseHandler *hdlr ) { @@ -631,8 +631,8 @@ namespace XrdCl //------------------------------------------------------------------------ //! A factory method, simply forwards the given handler //! - //! @param h : the ResponseHandler that should be wrapped - //! @return : a ForwardingHandler instance + //! @param hdlr : the ResponseHandler that should be wrapped + //! @return : a ForwardingHandler instance //------------------------------------------------------------------------ inline static ResponseHandler* Create( ResponseHandler &hdlr ) { @@ -687,7 +687,7 @@ namespace XrdCl //------------------------------------------------------------------------ //! A factory method //! - //! @param func : the task that should be wrapped + //! @param task : the task that should be wrapped //! @return : TaskWrapper instance //------------------------------------------------------------------------ template @@ -736,7 +736,7 @@ namespace XrdCl //------------------------------------------------------------------------ //! A factory method //! - //! @param func : the task that should be wrapped + //! @param task : the task that should be wrapped //! @return : TaskWrapper instance //------------------------------------------------------------------------ template diff --git a/src/XrdCl/XrdClOperations.hh b/src/XrdCl/XrdClOperations.hh index cdf056d0ede..24aa66a09c7 100644 --- a/src/XrdCl/XrdClOperations.hh +++ b/src/XrdCl/XrdClOperations.hh @@ -70,7 +70,6 @@ namespace XrdCl //! Constructor. //! //! @param handler : the handler of our operation - //! @param recovery : the recovery procedure for our operation //------------------------------------------------------------------------ PipelineHandler( ResponseHandler *handler ); @@ -244,13 +243,8 @@ namespace XrdCl //------------------------------------------------------------------------ //! Run operation //! - //! @param prom : the promise that we will have a result + //! @param prms : the promise that we will have a result //! @param final : the object to call at the end of pipeline - //! @param args : forwarded arguments - //! @param bucket : number of the bucket with arguments - //! - //! @return : stOK if operation was scheduled for execution - //! successfully, stError otherwise //------------------------------------------------------------------------ void Run( Timeout timeout, std::promise prms, @@ -290,7 +284,6 @@ namespace XrdCl //! @param params : container with parameters forwarded from //! previous operation //! @return : status of the operation - //! @param bucket : number of the bucket with arguments //------------------------------------------------------------------------ virtual XRootDStatus RunImpl( PipelineHandler *handler, uint16_t timeout ) = 0; @@ -528,6 +521,7 @@ namespace XrdCl //! the status //! //! @param pipeline : the pipeline to be executed + //! @param timeout : the pipeline timeout //! //! @return : status of the operation //---------------------------------------------------------------------------- @@ -583,7 +577,7 @@ namespace XrdCl //! Note: due to reference collapsing this covers both l-value and //! r-value references. //! - //! @param func : function/functor/lambda + //! @param hdlr : function/functor/lambda //------------------------------------------------------------------------ template Derived operator>>( Hdlr &&hdlr ) @@ -699,8 +693,8 @@ namespace XrdCl //------------------------------------------------------------------------ //! Implements operator>> functionality //! - //! @param h : handler to be added - //! @ + //! @param handler : handler to be added + //! //! @return : return an instance of Derived //------------------------------------------------------------------------ inline Derived StreamImpl( ResponseHandler *handler ) diff --git a/src/XrdCl/XrdClPostMasterInterfaces.hh b/src/XrdCl/XrdClPostMasterInterfaces.hh index b567fcb6c25..187a9ce581b 100644 --- a/src/XrdCl/XrdClPostMasterInterfaces.hh +++ b/src/XrdCl/XrdClPostMasterInterfaces.hh @@ -188,7 +188,7 @@ namespace XrdCl //! 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 + //! @param bytesWritten number of bytes written 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 @@ -228,7 +228,6 @@ namespace XrdCl //! 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 diff --git a/src/XrdCl/XrdClSocket.hh b/src/XrdCl/XrdClSocket.hh index 5b6d5e9f963..19739551c4f 100644 --- a/src/XrdCl/XrdClSocket.hh +++ b/src/XrdCl/XrdClSocket.hh @@ -160,17 +160,17 @@ namespace XrdCl //------------------------------------------------------------------------ //! 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 + //! @param buffer : data to be written + //! @param size : size of the data buffer + //! @param bytesWritten : the amount of data actually written //------------------------------------------------------------------------ XRootDStatus Send( const char *buffer, size_t size, int &bytesWritten ); //------------------------------------------------------------------------ //! Write data from a kernel buffer to the socket //! - //! @param kbuff : data to be written - //! @return : the amount of data actually written + //! @param kbuff : data to be written + //! @param bytesWritten : the amount of data actually written //------------------------------------------------------------------------ XRootDStatus Send( XrdSys::KernelBuffer &kbuff, int &bytesWritten ); @@ -179,7 +179,6 @@ namespace XrdCl //! //! @param msg : message (request) to be sent //! @param strmname : stream name (for logging purposes) - //! @return : the amount of data actually written //------------------------------------------------------------------------ XRootDStatus Send( Message &msg, const std::string &strmname ); diff --git a/src/XrdCl/XrdClXCpCtx.hh b/src/XrdCl/XrdClXCpCtx.hh index 67c9ced15fa..5e726420f06 100644 --- a/src/XrdCl/XrdClXCpCtx.hh +++ b/src/XrdCl/XrdClXCpCtx.hh @@ -147,7 +147,7 @@ class XCpCtx * 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, + * @return : stError if we failed to transfer the file, * stOK otherwise, with one of the following codes: * - suDone : the whole file has been transferred, * we are done diff --git a/src/XrdCl/XrdClXCpSrc.hh b/src/XrdCl/XrdClXCpSrc.hh index d8c2e687f68..50e9ee437c1 100644 --- a/src/XrdCl/XrdClXCpSrc.hh +++ b/src/XrdCl/XrdClXCpSrc.hh @@ -218,7 +218,7 @@ class XCpSrc * This method is used by ChunkHandler to report the result of a write, * to the source object. * - * @param stats : operation status + * @param status : operation status * @param chunk : the read chunk (if operation failed, should be null) * @param handle : the file object used to read the chunk */ diff --git a/src/XrdCl/XrdClXRootDMsgHandler.hh b/src/XrdCl/XrdClXRootDMsgHandler.hh index 73c90edf23e..ea9c35176a8 100644 --- a/src/XrdCl/XrdClXRootDMsgHandler.hh +++ b/src/XrdCl/XrdClXRootDMsgHandler.hh @@ -282,7 +282,6 @@ namespace XrdCl //! 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, @@ -304,7 +303,7 @@ namespace XrdCl //! 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 + //! @param bytesWritten number of bytes written 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 @@ -510,7 +509,7 @@ namespace XrdCl //! Check if for given request and Metalink redirector it is OK to omit //! the kXR_wait and proceed stright to the next entry in the Metalink file //! - //! @param reuqest : the request in question + //! @param request : the request in question //! @param url : metalink URL //! @return : true if yes, false if no //------------------------------------------------------------------------ @@ -533,7 +532,7 @@ namespace XrdCl //! Read data from buffer //! //! @param buffer : the buffer with data - //! @param size : the size of the buffer + //! @param buflen : the size of the buffer //! @param result : output parameter (data read) //! @return : status of the operation //------------------------------------------------------------------------ @@ -544,7 +543,7 @@ namespace XrdCl //! Read a string from buffer //! //! @param buffer : the buffer with data - //! @param size : the size of the buffer + //! @param buflen : the size of the buffer //! @param result : output parameter (data read) //! @return : status of the operation //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClZipArchive.hh b/src/XrdCl/XrdClZipArchive.hh index 24fb0c381f8..d0ed61a2707 100644 --- a/src/XrdCl/XrdClZipArchive.hh +++ b/src/XrdCl/XrdClZipArchive.hh @@ -395,7 +395,6 @@ namespace XrdCl //----------------------------------------------------------------------- //! Append data to a new file, implementation //! - //! @param lfh : the Local File Header record //! @param size : number of bytes to be appended //! @param buffer : the buffer with the data to be appended //! @param handler : user callback diff --git a/src/XrdCl/XrdClZipOperations.hh b/src/XrdCl/XrdClZipOperations.hh index dd848dc1429..7bf7a41339f 100644 --- a/src/XrdCl/XrdClZipOperations.hh +++ b/src/XrdCl/XrdClZipOperations.hh @@ -33,7 +33,7 @@ namespace XrdCl //------------------------------------------------------------------------ //! Constructor //! - //! @param f : file on which the operation will be performed + //! @param zip : file on which the operation will be performed //! @param args : file operation arguments //------------------------------------------------------------------------ ZipOperation( Ctx zip, Arguments... args): ConcreteOperation( std::move( args )... ), zip( std::move( zip ) ) diff --git a/src/XrdCms/XrdCmsUtils.hh b/src/XrdCms/XrdCmsUtils.hh index e21e6b283c0..0a9d2e6b951 100644 --- a/src/XrdCms/XrdCmsUtils.hh +++ b/src/XrdCms/XrdCmsUtils.hh @@ -88,7 +88,7 @@ bool ParseMan(XrdSysError *eDest, XrdOucTList **oldMans, //! Obtain the port for a manager specification //! //! @param eDest Pointer to the error message object to route messages. -//! @param oldMans The configuration file stream. +//! @param CFile The configuration file stream. //! @param hSpec The initial manager specification which may or may not //! have the port number in it. //! diff --git a/src/XrdEc/XrdEcRedundancyProvider.hh b/src/XrdEc/XrdEcRedundancyProvider.hh index 1a942d5c649..b8724b02ad3 100644 --- a/src/XrdEc/XrdEcRedundancyProvider.hh +++ b/src/XrdEc/XrdEcRedundancyProvider.hh @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -//! @file RedundancyProvider.hh +//! @file XrdEcRedundancyProvider.hh //! @author Paul Hermann Lensing //! @brief Class for computing parities and recovering data //------------------------------------------------------------------------------ @@ -45,7 +45,7 @@ namespace XrdEc //! has to equal nData+nParity. Blocks can be arbitrary size, but size has //! to be equal within a stripe. Function will throw on incorrect input. //! - //! @param stripe nData+nParity blocks, missing (empty) blocks will be + //! @param stripes nData+nParity blocks, missing (empty) blocks will be //! computed if possible. //-------------------------------------------------------------------------- void compute( stripes_t &stripes ); @@ -77,8 +77,8 @@ namespace XrdEc //! to be correct (crc integrity checks of blocks should be done previously //! to attempting erasure decoding). //! - //! @param stripe vector of nData+nParity blocks, missing (empty) blocks are - //! errors + //! @param stripes vector of nData+nParity blocks, missing (empty) blocks + //! are errors //! @return a string of stripe size describing the error pattern //-------------------------------------------------------------------------- std::string getErrorPattern( stripes_t &stripes ) const; diff --git a/src/XrdNet/XrdNetCache.hh b/src/XrdNet/XrdNetCache.hh index f17637c1d47..ec50ee9f99b 100644 --- a/src/XrdNet/XrdNetCache.hh +++ b/src/XrdNet/XrdNetCache.hh @@ -66,7 +66,7 @@ char *Find(XrdNetAddrInfo *hAddr); //------------------------------------------------------------------------------ //! Set the default keep time for entries in the cache during initialization. //! -//! @param ktVal the number of seconds to keep an entry in the cache. +//! @param ktval the number of seconds to keep an entry in the cache. //------------------------------------------------------------------------------ static void SetKT(int ktval) {keepTime = ktval;} diff --git a/src/XrdNet/XrdNetIF.hh b/src/XrdNet/XrdNetIF.hh index 3adbb74d337..b369566222e 100644 --- a/src/XrdNet/XrdNetIF.hh +++ b/src/XrdNet/XrdNetIF.hh @@ -224,7 +224,7 @@ inline bool HasDest(ifType ifT=PublicV6) //------------------------------------------------------------------------------ //! Determine if an endpoint is this domain based on hostname. //! -//! @param epAddr Pointer to the endpoint NetAddrInfo object. +//! @param epaddr Pointer to the endpoint NetAddrInfo object. //! //! @result true The endpoint is in this domain. //! @result false Either the endpoint is not in this domain, is a private @@ -297,8 +297,6 @@ static void Privatize(ifType &x) {x = ifType(x | PrivateIF);} //! Set the default assigned port number. //! //! @param pnum The port number. -//! -//! @return The previous port number. //------------------------------------------------------------------------------ static void PortDefault(int pnum=1094); diff --git a/src/XrdNet/XrdNetMsg.hh b/src/XrdNet/XrdNetMsg.hh index ea3eaac3376..61ef9e8a751 100644 --- a/src/XrdNet/XrdNetMsg.hh +++ b/src/XrdNet/XrdNetMsg.hh @@ -58,7 +58,7 @@ public: //! computed as strlen(buff). //! @param dest The endpint name which can be host:port or a named socket. //! If dest is zero, uses dest specified in the constructor. -//! @param timeout maximum seconds to wait for a idle socket. When negative, +//! @param tmo maximum seconds to wait for a idle socket. When negative, //! the default, no time limit applies. //! @return <0 Message not sent due to error. //! @return =0 Message send (well as defined by UDP) @@ -78,7 +78,7 @@ int Send(const char *buff, // The data to be send //! computed as strlen(buff). //! @param dest The endpoint in the form as in "host:port". //! @param netSA The endpoint address. This overrides the constructor. -//! @param timeout maximum seconds to wait for a idle socket. When negative, +//! @param tmo maximum seconds to wait for a idle socket. When negative, //! the default, no time limit applies. //! @return <0 Message not sent due to error. //! @return =0 Message send (well as defined by UDP) @@ -98,7 +98,7 @@ int Send( const char *dest, // EP: host:port //! @param iovcnt The number of elements in the vector. //! @param dest The endpint name which can be host:port or a named socket. //! If dest is zero, uses dest specified in the constructor. -//! @param timeout maximum seconds to wait for a idle socket. When negative, +//! @param tmo maximum seconds to wait for a idle socket. When negative, //! the default, no time limit applies. //! @return <0 Message not sent due to error. //! @return =0 Message send (well as defined by UDP) diff --git a/src/XrdNet/XrdNetUtils.hh b/src/XrdNet/XrdNetUtils.hh index a0b95cc6b90..6a3d4ba4036 100644 --- a/src/XrdNet/XrdNetUtils.hh +++ b/src/XrdNet/XrdNetUtils.hh @@ -208,14 +208,14 @@ int GetSokInfo(int fd, char *theAddr, int theALen, char &theType); //! Obtain an easily digestable list of hosts. This is the list of up to eight //! unique aliases (i.e. with different addresses) assigned to a base hostname. //! -//! @param sPort If not nil, the *sPort will be set to hPort if and only if -//! the IP address in one of the entries matches the host -//! address. Otherwise, the value is unchanged. -//! @param hName the host specification suitable for XrdNetAddr.Set(). +//! @param hSpec the host specification suitable for XrdNetAddr.Set(). //! @param hPort When >= 0 specified the port to use regardless of hSpec. //! When < 0 the port must be present in hSpec. -//! hWant Maximum number of list entries wanted. If hWant is greater +//! @param hWant Maximum number of list entries wanted. If hWant is greater //! that eight it is set eigth. +//! @param sPort If not nil, the *sPort will be set to hPort if and only if +//! the IP address in one of the entries matches the host +//! address. Otherwise, the value is unchanged. //! @param eText When not nil, is where to place error message text. //! //! @return Success: Pointer to a list of XrdOucTList objects where @@ -379,7 +379,6 @@ static int Port(int fd, const char **eText=0); //! Obtain the protocol identifier. //! //! @param pName the name of the protocol (e.g. "tcp"). -//! @param eText when not null, the reason for a failure is returned. //! //! @return The protocol identifier. //------------------------------------------------------------------------------ @@ -404,7 +403,7 @@ static int ServPort(const char *sName, bool isUDP=false, const char **eText=0); //! used within this class and by XrdNetAddr when the IP mode changes. It is //! meant for internal use only. //! -//! @param ipType Is one of the following from the AddrOpts enum: +//! @param aOpts Is one of the following from the AddrOpts enum: //! allIPMap - Use IPv6 and mapped IPv4 addrs (default) //! onlyIPv4 - Use only IPv4 addresses. //! prefAuto - Determine proper options based on configuration. @@ -417,7 +416,7 @@ static int SetAuto(AddrOpts aOpts=allIPMap); //------------------------------------------------------------------------------ //! Check if whether or not a host name represents more than one unique host. //! -//! @param hName the host specification suitable for XrdNetAddr.Set(). +//! @param hSpec the host specification suitable for XrdNetAddr.Set(). //! @param eText When not nil, is where to place error message text. //! //! @return True is this is a simple single host. False if the name represensts diff --git a/src/XrdOfs/XrdOfsCPFile.hh b/src/XrdOfs/XrdOfsCPFile.hh index b5eb83b5006..592d9f6f7c9 100644 --- a/src/XrdOfs/XrdOfsCPFile.hh +++ b/src/XrdOfs/XrdOfsCPFile.hh @@ -55,7 +55,7 @@ int Append(const char *data, off_t offset, int dlen); //----------------------------------------------------------------------------- //! Create a checkpoint //! -//! @param srcFN - Pointer to the name of the source file being checkpointed. +//! @param lfn - Pointer to the name of the source file being checkpointed. //! @param Stat - Reference to source file stat information. //! //! @return 0 upon success and -errno upon failure. @@ -168,7 +168,7 @@ static char *Target(const char *ckpfn); //----------------------------------------------------------------------------- //! Constructor //! -//! @param ckpfn - Pointer to the name of the checkpoint file to use. When +//! @param cfn - Pointer to the name of the checkpoint file to use. When //! supplied, creates are prohibited. //----------------------------------------------------------------------------- diff --git a/src/XrdOfs/XrdOfsConfigPI.hh b/src/XrdOfs/XrdOfsConfigPI.hh index e7c4c0c6772..8cebb1699df 100644 --- a/src/XrdOfs/XrdOfsConfigPI.hh +++ b/src/XrdOfs/XrdOfsConfigPI.hh @@ -93,7 +93,7 @@ bool Configure(XrdCmsClient *cmscP, XrdOucEnv *envP); //----------------------------------------------------------------------------- //! Configure the fsctl plugin. //! -//! @param cmsP Pointer to the cms plugin. +//! @param cmscP Pointer to the cms plugin. //! @param envP Pointer to the environment. //----------------------------------------------------------------------------- diff --git a/src/XrdOfs/XrdOfsHandle.cc b/src/XrdOfs/XrdOfsHandle.cc index 0ebec735323..963f17a5a6a 100644 --- a/src/XrdOfs/XrdOfsHandle.cc +++ b/src/XrdOfs/XrdOfsHandle.cc @@ -71,7 +71,7 @@ class XrdOfsHanOss : public XrdOssDF int pgWrite(XrdSfsAio* aioparm, uint64_t opts) {return wRC; } ssize_t Read(off_t, size_t) {return rRC; } ssize_t Read(void *, off_t, size_t) {return rRC; } - int Read(XrdSfsAio *aoip) {return rRC; } + int Read(XrdSfsAio *aiop) {return rRC; } ssize_t ReadV(XrdOucIOVec *readV,int rdvcnt) {return rRC; } ssize_t ReadRaw( void *, off_t, size_t) {return rRC; } ssize_t Write(const void *, off_t, size_t) {return wRC; } diff --git a/src/XrdOfs/XrdOfsPrepare.hh b/src/XrdOfs/XrdOfsPrepare.hh index a8b826c7b34..f2b4426bf23 100644 --- a/src/XrdOfs/XrdOfsPrepare.hh +++ b/src/XrdOfs/XrdOfsPrepare.hh @@ -178,7 +178,7 @@ extern "C" XrdOfsPrepare_t *XrdOfsgetPrepare; //! @param parms -> Argument string specified on the namelib directive. It may //! be null or point to a null string if no parms exist. //! @param theSfs-> Pointer to the XrdSfsFileSystem plugin. -//! @param theOSs-> Pointer to the OSS plugin. +//! @param theOss-> Pointer to the OSS plugin. //! @param envP -> Pointer to environmental information (may be nil). //! @param prepP -> Pointer to the existing XrdOfsPrepare object that should //! be wrapped by the returned object. diff --git a/src/XrdOss/XrdOss.hh b/src/XrdOss/XrdOss.hh index 1268384f562..e1892ade9b5 100644 --- a/src/XrdOss/XrdOss.hh +++ b/src/XrdOss/XrdOss.hh @@ -104,7 +104,7 @@ virtual int Readdir(char *buff, int blen) {return -ENOTDIR;} //! deleted from the target directory are quietly skipped. //----------------------------------------------------------------------------- -virtual int StatRet(struct stat *) {return -ENOTSUP;} +virtual int StatRet(struct stat *buff) {return -ENOTSUP;} /******************************************************************************/ /* F i l e O r i e n t e d M e t h o d s */ @@ -161,7 +161,7 @@ virtual int Fsync(XrdSfsAio *aiop) {return -EISDIR;} //! @return 0 upon success or -errno or -osserr (see XrdOssError.hh). //----------------------------------------------------------------------------- -virtual int Ftruncate(unsigned long long) {return -EISDIR;} +virtual int Ftruncate(unsigned long long flen) {return -EISDIR;} //----------------------------------------------------------------------------- //! Return the memory mapped characteristics of the file. @@ -267,7 +267,7 @@ virtual ssize_t pgWrite(void* buffer, off_t offset, size_t wrlen, //! (see XrdOssError.hh). //----------------------------------------------------------------------------- -virtual int pgWrite(XrdSfsAio* aoiparm, uint64_t opts); +virtual int pgWrite(XrdSfsAio* aioparm, uint64_t opts); //----------------------------------------------------------------------------- //! Preread file blocks into the file system cache. @@ -303,7 +303,7 @@ virtual ssize_t Read(void *buffer, off_t offset, size_t size) //! (see XrdOssError.hh). //----------------------------------------------------------------------------- -virtual int Read(XrdSfsAio *aoip) {(void)aoip; return (ssize_t)-EISDIR;} +virtual int Read(XrdSfsAio *aiop) {(void)aiop; return (ssize_t)-EISDIR;} //----------------------------------------------------------------------------- //! Read uncompressed file bytes into a buffer. @@ -555,7 +555,8 @@ virtual void Connect(XrdOucEnv &env); //! @return 0 upon success or -errno or -osserr (see XrdOssError.hh). //----------------------------------------------------------------------------- -virtual int Create(const char *, const char *, mode_t, XrdOucEnv &, +virtual int Create(const char *tid, const char *path, + mode_t mode, XrdOucEnv &env, int opts=0)=0; //----------------------------------------------------------------------------- @@ -652,7 +653,7 @@ virtual int Reloc(const char *tident, const char *path, //! Remove a directory. //! //! @param path - Pointer to the path of the directory to be removed. -//! @param opts - The processing options: +//! @param Opts - The processing options: //! XRDOSS_Online - only remove online copy //! XRDOSS_isPFN - path is already translated. //! @param envP - Pointer to environmental information. @@ -711,9 +712,6 @@ virtual int Stats(char *buff, int blen) {(void)buff; (void)blen; return 0; //! @param buff - Pointer to the buffer to hold the information. //! @param blen - Length of the buffer. This is updated with the actual //! number of bytes placed in the buffer as in snprintf(). -//! @param opts - Options: -//! XRDEXP_STAGE - info for stageable space wanted. -//! XRDEXP_NOTRW - info for Read/Only space wanted. //! @param envP - Pointer to environmental information. //! //! @return " " @@ -730,16 +728,14 @@ virtual int StatFS(const char *path, char *buff, int &blen, //----------------------------------------------------------------------------- //! Return filesystem physical space information associated with a space name. //! +//! @param env - Ref to environmental information. If the environment +//! has the key oss.cgroup defined, the associated value is +//! used as the space name and the path is ignored. //! @param path - Path in the name space in question. The space name //! associated with gthe path is used unless overridden. //! @param buff - Pointer to the buffer to hold the information. //! @param blen - Length of the buffer. This is updated with the actual //! number of bytes placed in the buffer as in snprintf(). -//! @param opts - Options (see StatFS()) apply only when there are no -//! spaces defined. -//! @param envP - Ref to environmental information. If the environment -//! has the key oss.cgroup defined, the associated value is -//! used as the space name and the path is ignored. //! //! @return "oss.cgroup=&oss.space=&oss.free= //! &oss.maxf=&oss.used= @@ -850,7 +846,7 @@ virtual int Truncate(const char *path, unsigned long long fsize, //! Remove a file. //! //! @param path - Pointer to the path of the file to be removed. -//! @param opts - Options: +//! @param Opts - Options: //! XRDOSS_isMIG - this is a migratable path. //! XRDOSS_isPFN - do not apply name2name to path. //! XRDOSS_Online - remove only the online copy. diff --git a/src/XrdOss/XrdOssAt.hh b/src/XrdOss/XrdOssAt.hh index 1207cca939b..6bcfb17e694 100644 --- a/src/XrdOss/XrdOssAt.hh +++ b/src/XrdOss/XrdOssAt.hh @@ -131,7 +131,7 @@ int Unlink(XrdOssDF &atDir, const char *path); //----------------------------------------------------------------------------- //! Constructor //! -//! @param ossfd - Reference to the OSS system interface. +//! @param ossfs - Reference to the OSS system interface. //----------------------------------------------------------------------------- XrdOssAt(XrdOss &ossfs) : ossFS(ossfs) {} diff --git a/src/XrdOss/XrdOssStatInfo.hh b/src/XrdOss/XrdOssStatInfo.hh index 6fcb1022731..4b72ac0d060 100644 --- a/src/XrdOss/XrdOssStatInfo.hh +++ b/src/XrdOss/XrdOssStatInfo.hh @@ -35,6 +35,13 @@ class XrdOucEnv; class XrdSysLogger; struct stat; +namespace XrdOssStatEvent +{ +static const int FileAdded = 1; //!< Path has been added +static const int PendAdded = 2; //!< Path has been added in pending mode +static const int FileRemoved = 0; //!< Path has been removed +} + //------------------------------------------------------------------------------ //! This file defines the alternate stat() function that can be used as a //! replacement for the normal system stat() call that is used to determine the @@ -60,13 +67,6 @@ struct stat; //! @return Failure: a -1 with errno set to the correct err number value. //------------------------------------------------------------------------------ -typedef int (*XrdOssStatInfo_t) (const char *path, struct stat *buff, - int opts, XrdOucEnv *envP); - -typedef int (*XrdOssStatInfo2_t)(const char *path, struct stat *buff, - int opts, XrdOucEnv *envP, - const char *lfn); - //------------------------------------------------------------------------------ //! Set file information. //! @@ -78,18 +78,22 @@ typedef int (*XrdOssStatInfo2_t)(const char *path, struct stat *buff, //! @param path -> the file path whose whose stat information changed. //! @param buff -> Nil; this indicates that stat information is being set. //! @param opts One of the following options: -namespace XrdOssStatEvent -{ -static const int FileAdded = 1; //!< Path has been added -static const int PendAdded = 2; //!< Path has been added in pending mode -static const int FileRemoved = 0; //!< Path has been removed -} +//! XrdOssStatEvent::FileAdded, +//! XrdOssStatEvent::PendAdded, +//! XrdOssStatEvent::FileRemoved. //! @param envP -> Nil //! @param lfn -> the logical file name whose stat information changed. //! //! @return The return value should be zero but is not currently inspected. //------------------------------------------------------------------------------ +typedef int (*XrdOssStatInfo_t) (const char *path, struct stat *buff, + int opts, XrdOucEnv *envP); + +typedef int (*XrdOssStatInfo2_t)(const char *path, struct stat *buff, + int opts, XrdOucEnv *envP, + const char *lfn); + /******************************************************************************/ /* X r d O s s S t a t I n f o I n s t a n t i a t o r */ /******************************************************************************/ @@ -123,11 +127,12 @@ static const int FileRemoved = 0; //!< Path has been removed //! The function creator must be declared as an extern "C" function in the //! plug-in shared library as follows: //------------------------------------------------------------------------------ -/*! - extern "C" XrdOssStatInfo_t XrdOssStatInfoInit(XrdOss *native_oss, - XrdSysLogger *Logger, - const char *config_fn, - const char *parms); +/*! @code {.cpp} + extern "C" XrdOssStatInfo_t XrdOssStatInfoInit(XrdOss *native_oss, + XrdSysLogger *Logger, + const char *config_fn, + const char *parms); + @endcode 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. @@ -137,11 +142,13 @@ static const int FileRemoved = 0; //!< Path has been removed @param envP - Pointer to the environment containing implementation specific information. - extern "C" XrdOssStatInfo2_t XrdOssStatInfoInit2(XrdOss *native_oss, - XrdSysLogger *Logger, - const char *config_fn, - const char *parms, - XrdOucEnv *envP); + @code {.cpp} + extern "C" XrdOssStatInfo2_t XrdOssStatInfoInit2(XrdOss *native_oss, + XrdSysLogger *Logger, + const char *config_fn, + const char *parms, + XrdOucEnv *envP); + @endcode */ //------------------------------------------------------------------------------ @@ -151,7 +158,8 @@ static const int FileRemoved = 0; //!< Path has been removed //! your plug-in. Include the code shown below at file level in your source. //------------------------------------------------------------------------------ -/*! #include "XrdVersion.hh" +/*! + #include "XrdVersion.hh" XrdVERSIONINFO(XrdOssStatInfoInit,); where \ is a 1- to 15-character unquoted name identifying your plugin. diff --git a/src/XrdOss/XrdOssWrapper.hh b/src/XrdOss/XrdOssWrapper.hh index df82fc60783..4df3d792330 100644 --- a/src/XrdOss/XrdOssWrapper.hh +++ b/src/XrdOss/XrdOssWrapper.hh @@ -87,7 +87,7 @@ virtual int Readdir(char *buff, int blen) //! Set the stat() buffer where stat information is to be placed corresponding //! to the directory entry returned by Readdir(). //! -//! @param buff - Pointer to stat structure to be used. +//! @param Stat - Pointer to stat structure to be used. //! //! @return 0 upon success or -ENOTSUP if not supported. //! @@ -151,8 +151,8 @@ virtual int Fsync(XrdSfsAio *aiop) {return wrapDF.Fsync(aiop);} //! @return 0 upon success or -errno or -osserr (see XrdOssError.hh). //----------------------------------------------------------------------------- -virtual int Ftruncate(unsigned long long offs) - {return wrapDF.Ftruncate(offs);} +virtual int Ftruncate(unsigned long long flen) + {return wrapDF.Ftruncate(flen);} //----------------------------------------------------------------------------- //! Return the memory mapped characteristics of the file. @@ -609,7 +609,7 @@ virtual int Reloc(const char *tident, const char *path, //! Remove a directory. //! //! @param path - Pointer to the path of the directory to be removed. -//! @param opts - The processing options: +//! @param Opts - The processing options: //! XRDOSS_Online - only remove online copy //! XRDOSS_isPFN - path is already translated. //! @param envP - Pointer to environmental information. @@ -672,9 +672,6 @@ virtual int Stats(char *buff, int blen) //! @param buff - Pointer to the buffer to hold the information. //! @param blen - Length of the buffer. This is updated with the actual //! number of bytes placed in the buffer as in snprintf(). -//! @param opts - Options: -//! XRDEXP_STAGE - info for stageable space wanted. -//! XRDEXP_NOTRW - info for Read/Only space wanted. //! @param envP - Pointer to environmental information. //! //! @return " " @@ -692,16 +689,14 @@ virtual int StatFS(const char *path, char *buff, int &blen, //----------------------------------------------------------------------------- //! Return filesystem physical space information associated with a space name. //! +//! @param env - Ref to environmental information. If the environment +//! has the key oss.cgroup defined, the associated value is +//! used as the space name and the path is ignored. //! @param path - Path in the name space in question. The space name //! associated with gthe path is used unless overridden. //! @param buff - Pointer to the buffer to hold the information. //! @param blen - Length of the buffer. This is updated with the actual //! number of bytes placed in the buffer as in snprintf(). -//! @param opts - Options (see StatFS()) apply only when there are no -//! spaces defined. -//! @param envP - Ref to environmental information. If the environment -//! has the key oss.cgroup defined, the associated value is -//! used as the space name and the path is ignored. //! //! @return "oss.cgroup=&oss.space=&oss.free= //! &oss.maxf=&oss.used= @@ -807,7 +802,7 @@ virtual int Truncate(const char *path, unsigned long long fsize, //! Remove a file. //! //! @param path - Pointer to the path of the file to be removed. -//! @param opts - Options: +//! @param Opts - Options: //! XRDOSS_isMIG - this is a migratable path. //! XRDOSS_isPFN - do not apply name2name to path. //! XRDOSS_Online - remove only the online copy. diff --git a/src/XrdOuc/XrdOucBackTrace.hh b/src/XrdOuc/XrdOucBackTrace.hh index 5a00c619aa5..bde7d439c95 100644 --- a/src/XrdOuc/XrdOucBackTrace.hh +++ b/src/XrdOuc/XrdOucBackTrace.hh @@ -106,7 +106,7 @@ static void DoBT(const char *head=0, void *thisP=0, void *objP=0, //! login mkdir mv open ping prepare protocol putfile query read //! readv rm rmdir set stat statx sync truncate verifyw write //! -//! @param reqs The kXR_ response code name(s). If the pointer is nil, the +//! @param rsps The kXR_ response code name(s). If the pointer is nil, the //! back trace filter is set using envar XRDBT_RSPFILTER as the //! argument. If both are nil, no filter is established. //! Specify, one or more names, each separated by a space. diff --git a/src/XrdOuc/XrdOucBuffer.hh b/src/XrdOuc/XrdOucBuffer.hh index aef1abc64b5..ace08ab1d4d 100644 --- a/src/XrdOuc/XrdOucBuffer.hh +++ b/src/XrdOuc/XrdOucBuffer.hh @@ -201,7 +201,7 @@ inline int DataLen() {return dlen;} //----------------------------------------------------------------------------- //! Highjack the buffer contents and reinitialize the original buffer. //! -//! @param xsz - the desired size to be given to the highjacked buffer. If +//! @param bPsz - the desired size to be given to the highjacked buffer. If //! zero, the current size is used. Same size resictions apply //! as for buffer pool Alloc(), above. //! diff --git a/src/XrdOuc/XrdOucCRC.hh b/src/XrdOuc/XrdOucCRC.hh index b8a8139cb94..a59651d8fa8 100644 --- a/src/XrdOuc/XrdOucCRC.hh +++ b/src/XrdOuc/XrdOucCRC.hh @@ -74,8 +74,8 @@ static uint32_t Calc32C(const void* data, size_t count, uint32_t prevcs=0); //! @param csval Pointer to a vector to hold individual page checksums. The //! vector must be sized: //! (count/XrdSys::PageSize + (count%XrdSys::PageSize != 0)). -//! -//! @return Each element of csval holds the checksum for the associated page. +//! On return, each element of csval holds the checksum for +//! the associated page. //------------------------------------------------------------------------------ static void Calc32C(const void* data, size_t count, uint32_t* csval); diff --git a/src/XrdOuc/XrdOucCache.hh b/src/XrdOuc/XrdOucCache.hh index c99508f35c6..4b1222c1c1c 100644 --- a/src/XrdOuc/XrdOucCache.hh +++ b/src/XrdOuc/XrdOucCache.hh @@ -398,9 +398,6 @@ virtual int Trunc(long long offs) = 0; //! caller holds any locks they must be recursive locks as the //! callback may occur on the calling thread. //! @param offs the size the file is have. -//! -//! @return <0 - Trunc failed, value is -errno. -//! =0 - Trunc succeeded. //------------------------------------------------------------------------------ virtual void Trunc(XrdOucCacheIOCB &iocb, long long offs) @@ -720,7 +717,7 @@ virtual ~XrdOucCache() {} //------------------------------------------------------------------------------ //! Your cache plug-in must exist in a shared library and have the following -//! extern C function defined whos parameters are: +//! extern C function defined whose parameters are: //! //! @param Logger Pointer to the logger object that should be used with an //! instance of XrdSysError to direct messages to a log file. @@ -732,22 +729,24 @@ virtual ~XrdOucCache() {} //! path. If Parms is null, there are no parameters. //! @param envP Pointer to environmental information. The most relevant //! is whether or not gStream monitoring is enabled. +//! @code {.cpp} //! XrdXrootdGStream *gStream = (XrddXrootdGStream *) //! envP->GetPtr("pfc.gStream*"); +//! @endcode //! @return A usable, fully configured, instance of an XrdOucCache //! object upon success and a null pointer otherwise. This //! instance is used for all operations defined by methods in //! XrdOucCache base class. //! +//! @code {.cpp} //! extern "C" //! { //! XrdOucCache *XrdOucGetCache(XrdSysLogger *Logger, // Where messages go //! const char *Config, // Config file used //! const char *Parms, // Optional parm string -//! } XrdOucEnv *envP); // Optional environment - -typedef XrdOucCache *(*XrdOucCache_t)(XrdSysLogger *, const char *, - const char *, XrdOucEnv *); +//! XrdOucEnv *envP); // Optional environment +//! } +//! @endcode //------------------------------------------------------------------------------ //! Declare compilation version. @@ -756,9 +755,14 @@ typedef XrdOucCache *(*XrdOucCache_t)(XrdSysLogger *, const char *, //! your plug-in. Declare it as shown below. //------------------------------------------------------------------------------ -/*! #include "XrdVersion.hh" - XrdVERSIONINFO(XrdOucGetCache,); +/*! + #include "XrdVersion.hh" + XrdVERSIONINFO(XrdOucGetCache,); where is a 1- to 15-character unquoted name identifying your plugin. */ + +typedef XrdOucCache *(*XrdOucCache_t)(XrdSysLogger *Logger, const char *Config, + const char *Parms, XrdOucEnv *envP); + #endif diff --git a/src/XrdOuc/XrdOucERoute.hh b/src/XrdOuc/XrdOucERoute.hh index e9659cb2871..5aeeec95bc5 100644 --- a/src/XrdOuc/XrdOucERoute.hh +++ b/src/XrdOuc/XrdOucERoute.hh @@ -61,7 +61,7 @@ static int Format(char *buff, int blen, int ecode, const char *etxt1, //! @param estrm pointer to the XrdOucStrean object which is to receive the //! error message text or null if none exists. //! @param esfx The suffix identifier to use when routing to the log. -//! @param enum the error number associated iwth the error. +//! @param ecode the error number associated iwth the error. //! @param etxt1 associated text token #1. //! @param etxt2 associated text token #2 (optional). //! diff --git a/src/XrdOuc/XrdOucErrInfo.hh b/src/XrdOuc/XrdOucErrInfo.hh index e2f40a0a039..491ba9a1146 100644 --- a/src/XrdOuc/XrdOucErrInfo.hh +++ b/src/XrdOuc/XrdOucErrInfo.hh @@ -357,8 +357,6 @@ inline int getErrMid() {return mID;} //----------------------------------------------------------------------------- //! Set the monitoring identifier. -//! -//! @return The monitoring identifier. //----------------------------------------------------------------------------- inline void setErrMid(int mid) {mID = mid;} diff --git a/src/XrdOuc/XrdOucFileInfo.hh b/src/XrdOuc/XrdOucFileInfo.hh index 9710a569397..5897c3be4b5 100644 --- a/src/XrdOuc/XrdOucFileInfo.hh +++ b/src/XrdOuc/XrdOucFileInfo.hh @@ -97,7 +97,7 @@ void AddProtocol(const char * protname); //----------------------------------------------------------------------------- //! Obtain the next digest that can be used to validate the file. //! -//! @param hVal Place to put the pointer to the hash value in ASCII +//! @param hval Place to put the pointer to the hash value in ASCII //! encoded hex, //! @param xrdname When true the corresponding name expected by XRootD is //! returned diff --git a/src/XrdOuc/XrdOucGMap.hh b/src/XrdOuc/XrdOucGMap.hh index 0f93c867dff..a3532f32c55 100644 --- a/src/XrdOuc/XrdOucGMap.hh +++ b/src/XrdOuc/XrdOucGMap.hh @@ -155,8 +155,6 @@ int load(const char *mf, bool force = 0); //! otherwise system performance will be severely degraded. //------------------------------------------------------------------------------ -extern "C" XrdOucGMap *XrdOucgetGMap(XrdOucGMapArgs); - //------------------------------------------------------------------------------ //! Declare compilation version. //! @@ -165,9 +163,13 @@ extern "C" XrdOucGMap *XrdOucgetGMap(XrdOucGMapArgs); //! avoid execution issues should the class definition change. Declare it as: //------------------------------------------------------------------------------ -/*! #include "XrdVersion.hh" - XrdVERSIONINFO(XrdOucgetGMap,); +/*! + #include "XrdVersion.hh" + XrdVERSIONINFO(XrdOucgetGMap,); where is a 1- to 15-character unquoted name identifying your plugin. */ + +extern "C" XrdOucGMap *XrdOucgetGMap(XrdOucGMapArgs); + #endif diff --git a/src/XrdOuc/XrdOucGatherConf.hh b/src/XrdOuc/XrdOucGatherConf.hh index 9302115f642..ce5d6ecd1a7 100644 --- a/src/XrdOuc/XrdOucGatherConf.hh +++ b/src/XrdOuc/XrdOucGatherConf.hh @@ -83,7 +83,7 @@ bool hasData(); //------------------------------------------------------------------------------ //! Attempt to use pre-existing data. //! -//! @param Pointer to null terminated pre-existing data. +//! @param data Pointer to null terminated pre-existing data. //! //! @return False if the pointer is nil or points to a null string; true o/w. //------------------------------------------------------------------------------ diff --git a/src/XrdOuc/XrdOucPgrwUtils.hh b/src/XrdOuc/XrdOucPgrwUtils.hh index c71ab9fff3f..96c6937b6b9 100644 --- a/src/XrdOuc/XrdOucPgrwUtils.hh +++ b/src/XrdOuc/XrdOucPgrwUtils.hh @@ -47,8 +47,8 @@ public: //! @param csval Pointer to a vector to hold individual page checksums. The //! raw vector must be sized as computed by csNum(). When //! passed an std::vector, it is done automatically. -//! -//! @return Each element of csval holds the checksum for the associated page. +//! On return, each element of csval holds the checksum for +//! the associated page. //------------------------------------------------------------------------------ static void csCalc(const char* data, off_t offs, size_t count, diff --git a/src/XrdOuc/XrdOucPinKing.hh b/src/XrdOuc/XrdOucPinKing.hh index 55abaaaf9c5..f96a5fd47f3 100644 --- a/src/XrdOuc/XrdOucPinKing.hh +++ b/src/XrdOuc/XrdOucPinKing.hh @@ -54,7 +54,7 @@ public: //! Add an Pin object to the load list. //! //! @param path Pointer to the pin's path. -//! @param parm Pointer to the pin's parameters. +//! @param parms Pointer to the pin's parameters. //! @param push When true pushes the pin onto the load stack. Otherwise, //! replaces or defines the base plugin. //------------------------------------------------------------------------------ @@ -82,9 +82,9 @@ T *Load(const char *Symbol); //! @param drctv Ref to the directive that initiated the load. The text is //! used in error messages to relate the directive to the error. //! E.g. "sec.entlib" -> "Unable to load sec.entlib plugin...." -//! @param envP Ref to environment. -//! @param errP Ref to the message routing object. -//! @param vInfo Pointer to the version information of the caller. If the +//! @param envR Ref to environment. +//! @param errR Ref to the message routing object. +//! @param vinfo Pointer to the version information of the caller. If the //! pointer is nil, no version checking occurs. //------------------------------------------------------------------------------ diff --git a/src/XrdOuc/XrdOucVerName.hh b/src/XrdOuc/XrdOucVerName.hh index a160f9359ab..ba9e64fdb9e 100644 --- a/src/XrdOuc/XrdOucVerName.hh +++ b/src/XrdOuc/XrdOucVerName.hh @@ -43,7 +43,7 @@ public: //! Test if plugin path contains a version number. //! //! @param piPath Pointer to the original path to the plug-in. -//! @param piNoVm != 0: If piPath has a version, an strdup'd path without a +//! @param piNoVN != 0: If piPath has a version, an strdup'd path without a //! version is returned. Otherwise, nil is returned. //! == 0: Does not return an alternate path. //! diff --git a/src/XrdPosix/XrdPosixCache.hh b/src/XrdPosix/XrdPosixCache.hh index 38b968940ce..45efdf4d1f0 100644 --- a/src/XrdPosix/XrdPosixCache.hh +++ b/src/XrdPosix/XrdPosixCache.hh @@ -77,8 +77,8 @@ int Rmdir(const char* path); //----------------------------------------------------------------------------- //! Rename a file or directory in the cache. //! -//! @param oldpath -> filepath of existing directory or file. -//! @param newpath -> filepath the directory or file is to have. +//! @param oldPath -> filepath of existing directory or file. +//! @param newPath -> filepath the directory or file is to have. //! //! @return 0 This method is currently not supported. //----------------------------------------------------------------------------- @@ -101,7 +101,7 @@ int Stat(const char *path, struct stat &sbuff); //----------------------------------------------------------------------------- //! Rename a file or directory in the cache. //! -//! @param Stat Reference to the statistics object to be filled in. +//! @param Stats Reference to the statistics object to be filled in. //----------------------------------------------------------------------------- void Statistics(XrdOucCacheStats &Stats); diff --git a/src/XrdPosix/XrdPosixXrootd.hh b/src/XrdPosix/XrdPosixXrootd.hh index 836dd13525b..2902af5199e 100644 --- a/src/XrdPosix/XrdPosixXrootd.hh +++ b/src/XrdPosix/XrdPosixXrootd.hh @@ -322,9 +322,6 @@ static int Unlink(const char *path); //! @param n the number of elements in the readV vector. //! //! @param cbp pointer to the callback object for async execution. -//! -//! @return Upon success returns the total number of bytes read. Otherwise, -1 -//! is returned and errno is appropriately set. //----------------------------------------------------------------------------- static void VRead(int fildes, const XrdOucIOVec *readV, int n, diff --git a/src/XrdPss/XrdPss.hh b/src/XrdPss/XrdPss.hh index fe5bc3ca043..e864a0983cf 100644 --- a/src/XrdPss/XrdPss.hh +++ b/src/XrdPss/XrdPss.hh @@ -90,7 +90,7 @@ ssize_t pgRead (void* buffer, off_t offset, size_t rdlen, int pgRead (XrdSfsAio* aioparm, uint64_t opts); ssize_t pgWrite(void* buffer, off_t offset, size_t wrlen, uint32_t* csvec, uint64_t opts); -int pgWrite(XrdSfsAio* aoiparm, uint64_t opts); +int pgWrite(XrdSfsAio* aioparm, uint64_t opts); ssize_t Read( off_t, size_t); ssize_t Read( void *, off_t, size_t); int Read(XrdSfsAio *aiop); diff --git a/src/XrdRmc/XrdRmc.hh b/src/XrdRmc/XrdRmc.hh index 02c538a2888..bbf7b8d2d2d 100644 --- a/src/XrdRmc/XrdRmc.hh +++ b/src/XrdRmc/XrdRmc.hh @@ -38,53 +38,55 @@ cache. There can be many such instances. Each instance is associated with one or more XrdOucCacheIO objects (see the XrdOucCache::Attach() method). - Notes: 1. The minimum PageSize is 4096 (4k) and must be a power of 2. - The maximum PageSize is 16MB. - 2. The size of the cache is forced to be a multiple PageSize and - have a minimum size of PageSize * 256. - 3. The minimum external read size is equal to PageSize. - 4. Currently, only write-through caches are supported. - 5. The Max2Cache value avoids placing data in the cache when a read - exceeds the specified value. The minimum allowed is PageSize, which - is also the default. - 6. Structured file optimization allows pages whose bytes have been - fully referenced to be discarded; effectively increasing the cache. - 7. A structured cache treats all files as structured. By default, the - cache treats files as unstructured. You can over-ride the settings - on an individual file basis when the file's I/O object is attached - by passing the XrdOucCache::optFIS option, if needed. - 8. Write-in caches are only supported for files attached with the - XrdOucCache::optWIN setting. Otherwise, updates are handled - with write-through operations. - 9. A cache object may be deleted. However, the deletion is delayed - until all CacheIO objects attached to the cache are detached. - 10. The default maximum attached files is set to 8192 when isServer - has been specified. Otherwise, it is set at 256. - 11. When canPreRead is specified, the cache asynchronously handles - preread requests (see XrdOucCacheIO::Preread()) using 9 threads - when isServer is in effect. Otherwise, 3 threads are used. - 12. The max queue depth for prereads is 8. When the max is exceeded - the oldest preread is discarded to make room for the newest one. - 13. If you specify the canPreRead option when creating the cache you - can also enable automatic prereads if the algorithm is workable. - Otherwise, you will need to implement your own algorithm and - issue prereads manually using the XrdOucCacheIO::Preread() method. - 14. The automatic preread algorithm is (see aprParms): - a) A preread operation occurs when all of the following conditions - are satisfied: - o The cache CanPreRead option is in effect. - o The read length < 'miniRead' - ||(read length < 'maxiRead' && Offset == next maxi offset) - b) The preread page count is set to be readlen/pagesize and the - preread occurs at the page after read_offset+readlen. The page - is adjusted, as follows: - o If the count is < minPages, it is set to minPages. - o The count must be > 0 at this point. - c) Normally, pre-read pages participate in the LRU scheme. However, - if the preread was triggered using 'maxiRead' then the pages are - marked for single use only. This means that the moment data is - delivered from the page, the page is recycled. - 15. Invalid options silently force the use of the default. + Notes: + + 1. The minimum PageSize is 4096 (4k) and must be a power of 2. + The maximum PageSize is 16MB. + 2. The size of the cache is forced to be a multiple PageSize and + have a minimum size of PageSize * 256. + 3. The minimum external read size is equal to PageSize. + 4. Currently, only write-through caches are supported. + 5. The Max2Cache value avoids placing data in the cache when a read + exceeds the specified value. The minimum allowed is PageSize, which + is also the default. + 6. Structured file optimization allows pages whose bytes have been + fully referenced to be discarded; effectively increasing the cache. + 7. A structured cache treats all files as structured. By default, the + cache treats files as unstructured. You can over-ride the settings + on an individual file basis when the file's I/O object is attached + by passing the XrdOucCache::optFIS option, if needed. + 8. Write-in caches are only supported for files attached with the + XrdOucCache::optWIN setting. Otherwise, updates are handled + with write-through operations. + 9. A cache object may be deleted. However, the deletion is delayed + until all CacheIO objects attached to the cache are detached. + 10. The default maximum attached files is set to 8192 when isServer + has been specified. Otherwise, it is set at 256. + 11. When canPreRead is specified, the cache asynchronously handles + preread requests (see XrdOucCacheIO::Preread()) using 9 threads + when isServer is in effect. Otherwise, 3 threads are used. + 12. The max queue depth for prereads is 8. When the max is exceeded + the oldest preread is discarded to make room for the newest one. + 13. If you specify the canPreRead option when creating the cache you + can also enable automatic prereads if the algorithm is workable. + Otherwise, you will need to implement your own algorithm and + issue prereads manually using the XrdOucCacheIO::Preread() method. + 14. The automatic preread algorithm is (see aprParms): + 1. A preread operation occurs when all of the following conditions + are satisfied: + - The cache CanPreRead option is in effect. + - The read length < 'miniRead' + || (read length < 'maxiRead' && Offset == next maxi offset) + 2. The preread page count is set to be readlen/pagesize and the + preread occurs at the page after read_offset+readlen. The page + is adjusted, as follows: + - If the count is < minPages, it is set to minPages. + - The count must be > 0 at this point. + 3. Normally, pre-read pages participate in the LRU scheme. However, + if the preread was triggered using 'maxiRead' then the pages are + marked for single use only. This means that the moment data is + delivered from the page, the page is recycled. + 15. Invalid options silently force the use of the default. */ class XrdRmc @@ -136,8 +138,8 @@ Debug = 0x0003; //!< Produce some debug messages (levels 0, 1, 2, or 3) //----------------------------------------------------------------------------- //! Create an instance of a memory cache. //! -//! @param Reference to mandatory cache parameters. -//! @param Optional pointer to default automatic preread parameters. +//! @param Params Reference to mandatory cache parameters. +//! @param aprP Optional pointer to default automatic preread parameters. //! //! @return Success: a pointer to a new instance of the cache. //! Failure: a null pointer is returned and errno set to the reason. diff --git a/src/XrdSec/XrdSecInterface.hh b/src/XrdSec/XrdSecInterface.hh index 53fe8fe83d0..ea4ec12b5a2 100644 --- a/src/XrdSec/XrdSecInterface.hh +++ b/src/XrdSec/XrdSecInterface.hh @@ -376,9 +376,11 @@ virtual ~XrdSecProtocol() {} //! return true (the default is to return false). //------------------------------------------------------------------------------ -/*! extern "C" char *XrdSecProtocol

Init (const char who, +/*! @code {.cpp} + extern "C" char *XrdSecProtocol

Init (const char who, const char *parms, XrdOucErrInfo *einfo) {. . . .} + @endcode */ //------------------------------------------------------------------------------ @@ -386,7 +388,7 @@ virtual ~XrdSecProtocol() {} //! //! @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 +//! @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 end-point. When @@ -478,10 +480,10 @@ virtual ~XrdSecProtocol() {} //! Typedef to simplify the encoding of methods returning XrdSecProtocol. //------------------------------------------------------------------------------ -typedef XrdSecProtocol *(*XrdSecGetProt_t)(const char *, - XrdNetAddrInfo &, - XrdSecParameters &, - XrdOucErrInfo *); +typedef XrdSecProtocol *(*XrdSecGetProt_t)(const char *hostname, + XrdNetAddrInfo &endPoint, + XrdSecParameters §oken, + XrdOucErrInfo *einfo); /*! Example: diff --git a/src/XrdSec/XrdSecProtect.hh b/src/XrdSec/XrdSecProtect.hh index d42541dd8e6..e18e76e8056 100644 --- a/src/XrdSec/XrdSecProtect.hh +++ b/src/XrdSec/XrdSecProtect.hh @@ -92,7 +92,7 @@ virtual void Delete() {delete this;} //! 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 +//! @param 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. //! @@ -114,7 +114,7 @@ virtual int Secure(SecurityRequest *&newreq, //! 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. +//! @param 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. diff --git a/src/XrdSecsss/XrdSecsssID.hh b/src/XrdSecsss/XrdSecsssID.hh index 6275324358a..51c2ffac861 100644 --- a/src/XrdSecsss/XrdSecsssID.hh +++ b/src/XrdSecsss/XrdSecsssID.hh @@ -99,7 +99,7 @@ enum authType //! @param lgnid - Pointer to the login ID. //! @param Ident - Pointer to the entity object to be registstered. If the //! pointer is NIL, then the mapping is deleted. -//! @param doRep - When true, any existing mapping is replaced. +//! @param doReplace - When true, any existing mapping is replaced. //! @param defer - When true, the entity object is recorded but serialization //! is deferred until the object is needed. The entity object //! must remain valid until the mapping is deleted. The entity @@ -125,7 +125,7 @@ private: //! @param dP - Reference to a pointer where the serialized ID is returned. //! The caller is responsible for freeing the storage. //! @param myIP - Pointer to IP address of client. -//! @param opts - Options to pass to the XrdSecsssEnt data extractor. +//! @param dataOpts - Options to pass to the XrdSecsssEnt data extractor. //! See XrdSecsssEnt::rr_Data for details. //! //! @return The length of the structure pointed to by dP; zero if not found. @@ -136,7 +136,7 @@ int Find(const char *lid, char *&dP, const char *myIP, int dataOpts=0); //----------------------------------------------------------------------------- //! Get initial parameters for sss ID mapping. //! -//! @param atype - The authentication type used by this object. +//! @param aType - The authentication type used by this object. //! @param idP - Reference to a pointer where the default ID is returned. //! //! @return A pointer to this object if it was instantiated, otherwise nil. diff --git a/src/XrdSfs/XrdSfsGPFile.hh b/src/XrdSfs/XrdSfsGPFile.hh index 302270463ea..6241d21a481 100644 --- a/src/XrdSfs/XrdSfsGPFile.hh +++ b/src/XrdSfs/XrdSfsGPFile.hh @@ -72,8 +72,8 @@ void *rsvd3; //!< Reserved field //! @param emsg - An optional message further explaining the reason for the //! failure (highly recommended). //! -//! @return No value is returned but this object is deleted and no references -//! to the object should exist after return is made. +//! No value is returned but this object is deleted and no references +//! to the object should exist after return is made. //----------------------------------------------------------------------------- virtual void Finished(int rc, const char *emsg=0) = 0; diff --git a/src/XrdSfs/XrdSfsInterface.hh b/src/XrdSfs/XrdSfsInterface.hh index b0da000a3bf..31340182b6e 100644 --- a/src/XrdSfs/XrdSfsInterface.hh +++ b/src/XrdSfs/XrdSfsInterface.hh @@ -375,23 +375,23 @@ public: //----------------------------------------------------------------------------- //! 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_NOTPC do not allow TPC operation -//! 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). +//! @param fileName - Pointer to the path of the file to be opened. +//! @param openMode - 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_NOTPC do not allow TPC operation +//! 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 createMode - 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 //----------------------------------------------------------------------------- @@ -497,8 +497,8 @@ 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. +//! @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. //----------------------------------------------------------------------------- @@ -636,7 +636,7 @@ virtual int read(XrdSfsAio *aioparm) = 0; //! 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. +//! @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. @@ -693,7 +693,7 @@ virtual int write(XrdSfsAio *aioparm) = 0; //! 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. +//! @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. @@ -755,7 +755,7 @@ 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. +//! @param xioP - Pointer to the XrdSfsXio object to be used for buffer exchanges. //----------------------------------------------------------------------------- virtual void setXio(XrdSfsXio *xioP) { (void)xioP; } @@ -1226,7 +1226,7 @@ virtual int rename(const char *oPath, //----------------------------------------------------------------------------- //! Return state information on a file or directory. //! -//! @param path - Pointer to the path in question. +//! @param Name - 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). @@ -1303,7 +1303,7 @@ uint64_t FeatureSet; //!< Adjust features at initialization @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 + @param configFn - pointer to the path of the configuration file. If nil there is no configuration file. @param envP - Pointer to the environment containing implementation specific information. diff --git a/src/XrdSsi/XrdSsiLogger.hh b/src/XrdSsi/XrdSsiLogger.hh index 282c774a7d4..93032116188 100644 --- a/src/XrdSsi/XrdSsiLogger.hh +++ b/src/XrdSsi/XrdSsiLogger.hh @@ -47,7 +47,7 @@ public: //! @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. +//! @param txt1,txt2,txt3 the message to be added to the log. //----------------------------------------------------------------------------- static void Msg(const char *pfx, const char *txt1, @@ -86,7 +86,7 @@ static void Msgv(const char *pfx, const char *fmt, va_list aP); //! //! @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. +//! @param iovN the number of elements in the iovec. //----------------------------------------------------------------------------- static void Msgv(struct iovec *iovP, int iovN); diff --git a/src/XrdSsi/XrdSsiRequest.hh b/src/XrdSsi/XrdSsiRequest.hh index feed3db4d3f..f2a406ce47e 100644 --- a/src/XrdSsi/XrdSsiRequest.hh +++ b/src/XrdSsi/XrdSsiRequest.hh @@ -192,7 +192,7 @@ virtual bool ProcessResponse(const XrdSsiErrInfo &eInfo, //! @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. +//! false More data may remain in the stream. //----------------------------------------------------------------------------- virtual void ProcessResponseData(const XrdSsiErrInfo &eInfo, char *buff, @@ -263,7 +263,7 @@ virtual void RelRequestBuffer() {} //! 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. +//! @param dttl The detach time to live value. //----------------------------------------------------------------------------- inline void SetDetachTTL(uint32_t dttl) {detTTL = dttl;} diff --git a/src/XrdSsi/XrdSsiResponder.hh b/src/XrdSsi/XrdSsiResponder.hh index 7f446645c42..90d8b58d719 100644 --- a/src/XrdSsi/XrdSsiResponder.hh +++ b/src/XrdSsi/XrdSsiResponder.hh @@ -121,7 +121,7 @@ protected: //! (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 rqstR 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 diff --git a/src/XrdSsi/XrdSsiService.hh b/src/XrdSsi/XrdSsiService.hh index 73fec21407d..c1e35bc63af 100644 --- a/src/XrdSsi/XrdSsiService.hh +++ b/src/XrdSsi/XrdSsiService.hh @@ -149,10 +149,10 @@ virtual bool Prepare(XrdSsiErrInfo &eInfo, const XrdSsiResource &rDesc); //! @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). +//! 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, diff --git a/src/XrdSsi/XrdSsiShMap.hh b/src/XrdSsi/XrdSsiShMap.hh index b2b3537751d..f074c220350 100644 --- a/src/XrdSsi/XrdSsiShMap.hh +++ b/src/XrdSsi/XrdSsiShMap.hh @@ -112,7 +112,7 @@ 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 +//! @param key 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;} //! @@ -157,6 +157,8 @@ bool Attach(const char *path, ShMap_Access access, int tmo=-1); //! done, call Export() to make the new map visible, possibly replacing an //! any existing version of a map with the same name. //! +//! @param path Pointer to the file that is or will represent the map. +//! //! @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: diff --git a/src/XrdSys/XrdSysLogPI.hh b/src/XrdSys/XrdSysLogPI.hh index e1ff0968691..953399b16c1 100644 --- a/src/XrdSys/XrdSysLogPI.hh +++ b/src/XrdSys/XrdSysLogPI.hh @@ -59,7 +59,7 @@ typedef void (*XrdSysLogPI_t)(struct timeval const &mtime, //! 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 cfgn -> Configuration filename (nil if none). //! @param argv -> command line arguments after "-+xrdlog". //! @param argc number of command line arguments in argv. //! diff --git a/src/XrdSys/XrdSysLogger.hh b/src/XrdSys/XrdSysLogger.hh index ccd9730ff43..b33bf82ca88 100644 --- a/src/XrdSys/XrdSysLogger.hh +++ b/src/XrdSys/XrdSysLogger.hh @@ -139,7 +139,7 @@ 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. +//! @param tFIFO Pointer to the XrdOucTListFIFO where messages are saved. //! If the pointer is nil, capturing is turned off. //----------------------------------------------------------------------------- diff --git a/src/XrdSys/XrdSysXAttr.hh b/src/XrdSys/XrdSysXAttr.hh index 1c985c07ce2..bd4e79482e3 100644 --- a/src/XrdSys/XrdSysXAttr.hh +++ b/src/XrdSys/XrdSysXAttr.hh @@ -155,7 +155,7 @@ virtual int List(AList **aPL, const char *Path, int fd=-1, int getSz=0) = 0; //! 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. //! diff --git a/src/XrdTls/XrdTls.hh b/src/XrdTls/XrdTls.hh index d195dbd420c..ca6268d9eff 100644 --- a/src/XrdTls/XrdTls.hh +++ b/src/XrdTls/XrdTls.hh @@ -110,7 +110,7 @@ static void SetDebug(int opts, msgCB_t logP); //------------------------------------------------------------------------ //! Convert SSL error to TLS::RC code. //! -//! @param sslerr - the SSL error return code. +//! @param sslrc - the SSL error return code. //! //! @return The corresponding TLS::RC code. //------------------------------------------------------------------------ @@ -120,7 +120,7 @@ static RC ssl2RC(int sslrc); //------------------------------------------------------------------------ //! Convert SSL error to text. //! -//! @param sslerr - the SSL error return code. +//! @param sslrc - the SSL error return code. //! @param dflt - the default to be return when mapping does no exist. //! //! @return The corresponding text or the dflt string is returned. diff --git a/src/XrdTls/XrdTlsSocket.hh b/src/XrdTls/XrdTlsSocket.hh index d5bc85a5481..232f4b7041b 100644 --- a/src/XrdTls/XrdTlsSocket.hh +++ b/src/XrdTls/XrdTlsSocket.hh @@ -94,7 +94,7 @@ enum HS_Mode //------------------------------------------------------------------------ //! Accept an incoming TLS connection //! -//! @param eWhy - If not nil, receives the associated error message. +//! @param eMsg - If not nil, receives the associated error message. //! //! @return The appropriate TLS return code. //------------------------------------------------------------------------ diff --git a/src/XrdXrootd/XrdXrootdBridge.hh b/src/XrdXrootd/XrdXrootdBridge.hh index acc68e656d5..bf2a6f366bd 100644 --- a/src/XrdXrootd/XrdXrootdBridge.hh +++ b/src/XrdXrootd/XrdXrootdBridge.hh @@ -84,15 +84,15 @@ class Result; //! 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 +//! @param 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 +//! @param seceP a pointer to the XrdSecEntity object that describes the //! client's identity. -//! nameP An arbitrary 1-to-8 character client name. The Bridge will +//! @param 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 +//! @param 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 @@ -179,7 +179,7 @@ virtual bool Disc() = 0; //! 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. +//! @param seton When true, enables sendfile() otherwise it is disabled. //! //! @return =0 Sucessful. //! @return <0 Call failed. The return code is -errno and usually will @@ -207,7 +207,7 @@ virtual int setSF(kXR_char *fhandle, bool seton=false) = 0; //! //----------------------------------------------------------------------------- -virtual void SetWait(int wime, bool notify=false) = 0; +virtual void SetWait(int wtime, 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 */ diff --git a/src/XrdXrootd/XrdXrootdGPFile.hh b/src/XrdXrootd/XrdXrootdGPFile.hh index b58d0d29da1..48e4fa98875 100644 --- a/src/XrdXrootd/XrdXrootdGPFile.hh +++ b/src/XrdXrootd/XrdXrootdGPFile.hh @@ -145,7 +145,7 @@ virtual void getFile(const XrdXrootdGPFileInfo &gargs, //! reflected by calling pargs.Completed() with an error code and msg. //----------------------------------------------------------------------------- -virtual void putFile(const XrdXrootdGPFileInfo &gargs, +virtual void putFile(const XrdXrootdGPFileInfo &pargs, const XrdSecEntity *client=0) = 0; //------------------------------------------------------------------------------ From 73fa7967ea8f03f54dd9eb4ac690fb28a791ca16 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sun, 28 Aug 2022 12:08:41 +0200 Subject: [PATCH 044/773] 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 f409b5bf61f72cd1abf9b639b51944cc7eef0555 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 29 Aug 2022 21:44:18 -0700 Subject: [PATCH 045/773] [Server] Avoid SEGV when client issues an invalid sequence. --- src/XrdXrootd/XrdXrootdFile.hh | 12 +++++++++--- src/XrdXrootd/XrdXrootdXeq.cc | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/XrdXrootd/XrdXrootdFile.hh b/src/XrdXrootd/XrdXrootdFile.hh index 7ba8f8dd287..fd28217c7e7 100644 --- a/src/XrdXrootd/XrdXrootdFile.hh +++ b/src/XrdXrootd/XrdXrootdFile.hh @@ -168,9 +168,15 @@ public: inline XrdXrootdFile *Get(int fnum) {if (fnum >= 0) - {if (fnum < XRD_FTABSIZE) return FTab[fnum]; - if (XTab && (fnum-XRD_FTABSIZE)Get(fh.handle)) - || fp == XrdXrootdFileTable::heldSpotP) + if (!FTab || !(fp = FTab->Get(fh.handle))) return Response.Send(kXR_FileNotOpen, "close does not refer to an open file"); From 0b01c887ca1e147e4fa7ff0a7dc0dd3858c7d2ad Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 1 Sep 2022 12:23:28 +0200 Subject: [PATCH 046/773] [Docs] Sync release notes. --- docs/PreReleaseNotes.txt | 92 ---------------------------------------- docs/ReleaseNotes.txt | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 92 deletions(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index ade623ba0d6..67abaf92dbf 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,101 +6,9 @@ Prerelease Notes ================ + **New Features** - **[Server]** Allow disablement of the tardy async I/O timeout path. - **Commits: 0e05f20e - **[Apps]** Provide command line too to manipulate checksum xattr. - **Commits: 0daa1ee3 - **[Protocol]** Add reflink capability to protocol via kXR_open options. - **Commits: 0a4ba6e2 - **[Server] Separate out authorization to overwrite data. - **Commits: 070ec697 - **[Server] Allow set variable values to come from a file. - **Commits: 0bcacd4a - **[Server]** Implement gStream to monitor all http and xroot TPC events. - **Commits: 00fadbee - **[Server]** Bring packet marking up to current specification. - **Commits: 0d7f8633 - **[Server]** Provide g-stream monitoring for Third Party Copy (TPC). - **Commits: 0cda247c - **[SciTokens]** Allow the SciToken plugin to consume based on ZTN tokens. - **Commits: 0aa7a0eb - **[Server]** Report experiment and activity codes when present for monitoring. - **Commits: 0bf2833d - **[HTTP]** Have the XrdHttp extraction logic match GSI/ - **Commits: 0379ca64 - **[XrdAcc]** Make the acc subsystem aware of request-based name mapping. - **Commits: 09b95bb0 - **[XrdFfs]** update xrootdfs to work with XrdEC faster - **Commits: e8493d27 - **[Posix]** Make xrootd proxy, xrootdfs and xrdadler32 work with XrdCl+EC - **Commits: 8a222633 - **[SciTokens]** Save token subject as an XrdSecEntity xattr - **Commits: d737d741 - **[Throttle]** Track maximum concurrency limits in throttle plugin - **Commits: ded8082e - **[XrdCl]** xrdfs: support multiple rm paths - **[XrdCl]** record / replay plug-in - **Commits: ???????? - **[XrdCl]** In EC, add adjustable preference to servers based on free space - **[XrdFfs]** same above - **Commits: 963ac979, 446b404e + **Major bug fixes** - **[Server]** Adjust for self-move behaviour changes in some compilers. - **Commits: 07f4217 - **[Server]** Reset the buffer pointer after a non-aligned pgRead request - **Commits: 3ee3b9e - **[Server]** Return correct pgread offset for sync reads. - **Commits: 989d204 - **[Server]** Modify vector's size instead of capacity to avoid undef behaviour - **Commits: 0bfc0ca2 + **Minor bug fixes** - **[Frm]** Fix incorrect logic in frm_admin audit space. - &&Commits: 0becd718 - **[Server]** Avoid SEGV during client recovery dur to close waitresp. - **Commits: 4df4cda9 - **[HTTP]** Use 405 for mkcol/mkdir EEXIST. - **Commits: 0a63a309 - **[Proxy]** Allow for URLs with username. - **Commits: 05a8c0ed - **[XrdPss]** Do not trigger DeepLocate when pss.origin is http(s) - **Commits: 5550a77c - **[XrdPosix]** bug fix, report correct st_blocks in EC - **COmmits: 99d44728 + **Miscellaneous** - **[Crypto]** Load "legacy" provider for blowfish in openssl v3 - **Commits: 8692538 - **[x509]** Allow commans in DN's. - **Commits: 6729eaa - **{SciTokens]** Add addition messages and debugging. - **Commits: ab9fb7a - **[Utils]** Avoid emitting fatal polling error message unless aborting. - **Commits: 41f2a89 - **[Server]** Avoid misleading error message due to queued but delayed event. - **Commits: 6695a44 - **[SciTomkens]** Also grant Readdir when token grants read permission. - **Commits: 8b05e3e - **[Tests]** Fix strcpy overflow. - **Commits: 4e112ac - **[HttpTpc]** Vector cleared after use so it can be shrunk. - **Commits: ebf9de7 - **[ZTN]** Point to the token via Entity.creds. - **Commits: 5e84e9f - **[Server]** Fix MacOS complaints about unused parameters. - **Commits: 7f2a61e - **[Oss]** Do not fail a mkdir if directory already exists with the same mode. - **Commits: fc97a7e - **[GSI]** Increase default kel length bits from 512 to 2048. - **Commits: f38bbd9 - **[TLS]** Make sure openssl messages always get logged. - **Commits: b2a4027 - **[GSI]** Comment out no longer needed trace record - **Commits: 92a1b7a - **[Server]** Ignore -Warray-bounds warnings from stricter check in gcc 12. - **Commits: 1639703 - **[TLS]** Display all OpenSSL messages upon fatal error. - **Commits: 14732fd - **[XrdPosix] fix a bug that return wrong info about file size in EC - **Commits: bee4ae7 diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 0405777da69..129b89f7af8 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -6,6 +6,90 @@ Release Notes ============= +------------- +Version 5.5.0 +------------- + ++ **New Features** + **[XrdApps]** Provide command line too to manipulate checksum xattr. + **[XrdApps]** xrdreplay: support quoted columns + **[XrdApps]** xrdrecorder: allow to set the output path using XRD_RECORDERPATH + envar. + **[Protocol]** Add reflink capability to protocol via kXR_open options. + **[Server] Separate out authorization to overwrite data. + **[Server] Allow set variable values to come from a file. + **[Server]** Implement gStream to monitor all http and xroot TPC events. + **[Server]** Bring packet marking up to current specification. + **[Server]** Provide g-stream monitoring for Third Party Copy (TPC). + **[SciTokens]** Allow the SciToken plugin to consume based on ZTN tokens. + **[Server]** Report experiment and activity codes when present for monitoring. + **[HTTP]** Have the XrdHttp extraction logic match GSI/ + **[XrdAcc]** Make the acc subsystem aware of request-based name mapping. + **[XrdFfs]** update xrootdfs to work with XrdEC faster + **[Posix]** Make xrootd proxy, xrootdfs and xrdadler32 work with XrdCl+EC + **[SciTokens]** Save token subject as an XrdSecEntity xattr + **[Throttle]** Track maximum concurrency limits in throttle plugin + **[XrdCl]** xrdfs: support multiple rm paths + **[XrdCl]** record / replay plug-in + **[XrdCl]** In EC, add adjustable preference to servers based on free space + **[XrdCl]** Add recorder plug-in and xrdreply tool. + **[XrdCl]** xrdcp --server: report IP stack to stderr. + **[XrdCl]** Introduce Stream queries (IpAddr, IpStack, HostName). + **[XrdCl]** Implement EC VectorRead. + **[XrdFfs]** same above + **[XrdVomsMapfile]** Add support for VOMS mapfile + **[XrdCl/XrdEc]** Make the remote ec cfg more flexible. + **[Pfc]** Implement async read and readV from the perspective of XrdOucCacheIO. + ++ **Major bug fixes** + **[Server]** Adjust for self-move behaviour changes in some compilers. + **[Server]** Modify vector's size instead of capacity to avoid undef behaviour + **[XrdEc]** Make sure returned read size is correct. + **[XrdEc]** Reader: make sure the completion handler is called if the read is of + zero size. + **[XrdCl]** Avoid race condition in AsyncSocketHander on use of reader/writer + objects after link is re-enabled + **[XrdCl]** Set the error status if the re-connection fails early during recovery + **[XrdCl]** xrdcp: don't use a common static status obj across all copy jobs. + **[XrdCl]** ZIP: respect file sizes > 4GB. + **[XrdCl]** Correctly calculate #pages in pgread rsp (for small rsp). + **[XrdCl]** PgRead: don't exceed max iovcnt. + **[XrdCl]** Avoid that pgread responses could be timedout while being processed. + **[XrdCl]** Avoid situation where client does not read all of a network message. + **[XrdCl]** Avoid race by using TimeOutSID in single place. + **[Server]** Reset the buffer pointer after a non-aligned pgRead request. + **[XrdPfc]** Count number of active reads on an PfcIO object so that POSIX AIO + bailout detach can be handled correctly. + **[XrdPfc]** Do early exit when prefetching of a block fails with no other + subscribers. + **[XrdHttp]** Map kXR_ItExists to HTTP 409. + **[XrdAcc]** Fix overwrite return code. + ++ **Minor bug fixes** + **[Frm]** Fix incorrect logic in frm_admin audit space. + **[Server]** Avoid SEGV during client recovery due to close waitresp. + **[Server]** Allow disablement of the tardy async I/O timeout path. + **[Proxy]** Allow for URLs with username. + **[XrdPss]** Do not trigger DeepLocate when pss.origin is http(s) + **[XrdPosix]** bug fix, report correct st_blocks in EC + ++ **Miscellaneous** + **[SciTokens]** Add addition messages and debugging. + **[SciTokens]** Also grant Readdir when token grants read permission. + **[Server]** Ignore -Warray-bounds warnings from stricter check in gcc 12. + **[CMake]** XRootDOSDefs: Use define_default on default values + **[CMake]** Add XrdOuc/XrdOucPgrwUtils.hh to private headers. + **[CMake]** Change Py required version to 3. + **[CI]** Add Ubunty Jammy builds. + **[XrdClHttp]** Move to xrootd core. + **[XrdCl]** Refactor kXR_read raw data socket readout. + **[XrdCl]** Support HostList in lambda completion handlers. + **[XrdCl]** Make sure FileStateHandler is preserved until all outstanding + requests are resolved. + **[XrdCl]** Make sure FS data are preserved until all outstanding requests + are resolved. + **[Crypto]** bf32: Load "legacy" provider for blowfish in openssl v3. + -------------- Version 5.4.3 -------------- From 360e0d47adc519bb56e00de6df55eb6acb5ce94f Mon Sep 17 00:00:00 2001 From: Wei Yang Date: Fri, 2 Sep 2022 21:35:25 -0700 Subject: [PATCH 047/773] Fix a bug in xrootdfs reported by issue #1777 --- src/XrdFfs/XrdFfsXrootdfs.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/XrdFfs/XrdFfsXrootdfs.cc b/src/XrdFfs/XrdFfsXrootdfs.cc index 9c7e3152fbd..77dd9f9bb4a 100644 --- a/src/XrdFfs/XrdFfsXrootdfs.cc +++ b/src/XrdFfs/XrdFfsXrootdfs.cc @@ -143,8 +143,6 @@ static void* xrootdfs_init(struct fuse_conn_info *conn) strcat(exportpath, next); } setenv("XRDEXPORTS", exportpath, 1); - if (savptr) free(savptr); - if (next) free(next); /* From FAQ: Miscellaneous threads should be started from the init() method. From 48c61b506fee92539aa9c0aed9e33f8b998ea4a6 Mon Sep 17 00:00:00 2001 From: Wei Yang Date: Fri, 2 Sep 2022 21:41:25 -0700 Subject: [PATCH 048/773] update PreReleaseNotes.txt --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 67abaf92dbf..be3e6093ed5 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -8,6 +8,8 @@ Prerelease Notes + **New Features** + **Major bug fixes** + **[XrdFfs]** Fix a bug in xrootdfs reported by issue #1777 + **Commit** 360e0d47 + **Minor bug fixes** From 37bfb705b2cde9c707251d7793cb303dabb60878 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Mon, 5 Sep 2022 14:04:30 +0200 Subject: [PATCH 049/773] [XrdCl] Introduce new error code for handling local errors. --- src/XrdCl/XrdClLocalFileHandler.cc | 34 +++++++++++++++--------------- src/XrdCl/XrdClStatus.cc | 1 + src/XrdCl/XrdClStatus.hh | 1 + 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/XrdCl/XrdClLocalFileHandler.cc b/src/XrdCl/XrdClLocalFileHandler.cc index 6124a11cd47..b06b80e152c 100644 --- a/src/XrdCl/XrdClLocalFileHandler.cc +++ b/src/XrdCl/XrdClLocalFileHandler.cc @@ -154,7 +154,7 @@ namespace { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, GetErrMsg( me->opcode ), XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); QueueTask( error, 0, me->hosts, me->handler ); @@ -250,7 +250,7 @@ namespace XrdCl { AnyObject *resp = 0; XRootDStatus st = OpenImpl( url, flags, mode, resp ); - if( !st.IsOK() && st.code != errErrorResponse ) + if( !st.IsOK() && st.code != errLocalError ) return st; return QueueTask( new XRootDStatus( st ), resp, handler ); @@ -275,7 +275,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Close: file fd: %i %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -296,7 +296,7 @@ namespace XrdCl if( fstat( fd, &ssp ) == -1 ) { log->Error( FileMsg, "Stat: failed fd: %i %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -311,7 +311,7 @@ namespace XrdCl { log->Error( FileMsg, "Stat: ParseServerResponse failed." ); delete statInfo; - return QueueTask( new XRootDStatus( stError, errErrorResponse, kXR_FSError ), + return QueueTask( new XRootDStatus( stError, errLocalError, kXR_FSError ), 0, handler ); } @@ -332,7 +332,7 @@ namespace XrdCl if( ( read = pread( fd, buffer, size, offset ) ) == -1 ) { log->Error( FileMsg, "Read: failed %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -380,7 +380,7 @@ namespace XrdCl if( ret == -1 ) { log->Error( FileMsg, "ReadV: failed %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -418,7 +418,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Write: failed %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -489,7 +489,7 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Truncate: failed, file descriptor: %i, %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -520,7 +520,7 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "VectorRead: failed, file descriptor: %i, %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -554,7 +554,7 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "VectorWrite: failed, file descriptor: %i, %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -597,7 +597,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "WriteV: failed %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, + XRootDStatus *error = new XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); return QueueTask( error, 0, handler ); @@ -816,7 +816,7 @@ namespace XrdCl int rc = lstat( tmp.c_str(), &st ); if( rc == 0 ) break; if( errno != ENOENT ) - return XRootDStatus( stError, errErrorResponse, + return XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); pos = path.rfind( '/', pos - 1 ); @@ -829,7 +829,7 @@ namespace XrdCl if( mkdir( tmp.c_str(), 0755 ) ) { if( errno != EEXIST ) - return XRootDStatus( stError, errErrorResponse, + return XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); } @@ -892,7 +892,7 @@ namespace XrdCl log->Error( FileMsg, "Open: open failed: %s: %s", path.c_str(), XrdSysE2T( errno ) ); - return XRootDStatus( stError, errErrorResponse, + return XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); } @@ -903,7 +903,7 @@ namespace XrdCl if( fstat( fd, &ssp ) == -1 ) { log->Error( FileMsg, "Open: stat failed." ); - return XRootDStatus( stError, errErrorResponse, + return XRootDStatus( stError, errLocalError, XProtocol::mapError( errno ), XrdSysE2T( errno ) ); } @@ -917,7 +917,7 @@ namespace XrdCl { log->Error( FileMsg, "Open: ParseServerResponse failed." ); delete statInfo; - return XRootDStatus( stError, errErrorResponse, kXR_FSError ); + return XRootDStatus( stError, errLocalError, kXR_FSError ); } // add the URL to hosts list diff --git a/src/XrdCl/XrdClStatus.cc b/src/XrdCl/XrdClStatus.cc index 8f6a381cc0d..e9b0cb4a26f 100644 --- a/src/XrdCl/XrdClStatus.cc +++ b/src/XrdCl/XrdClStatus.cc @@ -73,6 +73,7 @@ namespace { errInvalidResponse, "Invalid response" }, { errRedirect, "Unhandled redirect" }, { errErrorResponse, "Error response" }, + { errLocalError, "Local error" }, { errResponseNegative, "Query response negative" }, { 0, 0 } }; diff --git a/src/XrdCl/XrdClStatus.hh b/src/XrdCl/XrdClStatus.hh index bf8158254ec..8cd7e8e6d0b 100644 --- a/src/XrdCl/XrdClStatus.hh +++ b/src/XrdCl/XrdClStatus.hh @@ -104,6 +104,7 @@ namespace XrdCl const uint16_t errErrorResponse = 400; const uint16_t errRedirect = 401; + const uint16_t errLocalError = 402; const uint16_t errResponseNegative = 500; //!< Query response was negative From 0d10f558cab3ef4a1975c351cc2c646ad2b327a6 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Mon, 5 Sep 2022 16:26:55 +0200 Subject: [PATCH 050/773] [XrdCl] Allow to create mock sockets for testing. --- src/XrdCl/XrdClSocket.hh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/XrdCl/XrdClSocket.hh b/src/XrdCl/XrdClSocket.hh index 19739551c4f..984921706b3 100644 --- a/src/XrdCl/XrdClSocket.hh +++ b/src/XrdCl/XrdClSocket.hh @@ -63,7 +63,7 @@ namespace XrdCl //------------------------------------------------------------------------ //! Desctuctor //------------------------------------------------------------------------ - ~Socket(); + virtual ~Socket(); //------------------------------------------------------------------------ //! Initialize the socket @@ -164,7 +164,7 @@ namespace XrdCl //! @param size : size of the data buffer //! @param bytesWritten : the amount of data actually written //------------------------------------------------------------------------ - XRootDStatus Send( const char *buffer, size_t size, int &bytesWritten ); + virtual XRootDStatus Send( const char *buffer, size_t size, int &bytesWritten ); //------------------------------------------------------------------------ //! Write data from a kernel buffer to the socket @@ -193,7 +193,7 @@ namespace XrdCl //! EWOULDBLOCK : ( stOK, suRetry ) //! other error : ( stError, errSocketError ) //---------------------------------------------------------------------------- - XRootDStatus Read( char *buffer, size_t size, int &bytesRead ); + virtual XRootDStatus Read( char *buffer, size_t size, int &bytesRead ); //---------------------------------------------------------------------------- //! ReadV helper for raw socket From 33251d3463c0668c69263b309aa5a424fbdc6e99 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 6 Sep 2022 11:01:11 +0200 Subject: [PATCH 051/773] [XrdCl] copy job: fix memory leak (buffers not queued on error). --- src/XrdCl/XrdClClassicCopyJob.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClClassicCopyJob.cc b/src/XrdCl/XrdClClassicCopyJob.cc index d29f391670a..fd5750f3806 100644 --- a/src/XrdCl/XrdClClassicCopyJob.cc +++ b/src/XrdCl/XrdClClassicCopyJob.cc @@ -1834,7 +1834,10 @@ namespace { using namespace XrdCl; if( !pFile->IsOpen() ) + { + delete[] (char*)ci.GetBuffer(); // we took the ownership of the buffer return XRootDStatus( stError, errUninitialized ); + } //---------------------------------------------------------------------- // If there is still place for this chunk to be sent send it @@ -1856,6 +1859,7 @@ namespace log->Debug( UtilityMsg, "Unable write %d bytes at %ld from %s: %s", ch->chunk.GetLength(), ch->chunk.GetOffset(), pUrl.GetURL().c_str(), ch->status.ToStr().c_str() ); + delete[] (char*)ci.GetBuffer(); // we took the ownership of the buffer CleanUpChunks(); //-------------------------------------------------------------------- @@ -2661,7 +2665,6 @@ namespace XrdCl pResults->Set( "WrtRecoveryRedir", dest->GetWrtRecoveryRedir() ); return SetResult( st ); } - return DestinationError( st ); } From b473acfa960811be3d076e687463dc7f04be3ecf Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 6 Sep 2022 11:33:31 +0200 Subject: [PATCH 052/773] [XrdCl] local file handler: obtain error code with aio_error. --- src/XrdCl/XrdClLocalFileHandler.cc | 70 ++++++++++-------------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/src/XrdCl/XrdClLocalFileHandler.cc b/src/XrdCl/XrdClLocalFileHandler.cc index b06b80e152c..7399c8c42ed 100644 --- a/src/XrdCl/XrdClLocalFileHandler.cc +++ b/src/XrdCl/XrdClLocalFileHandler.cc @@ -152,11 +152,10 @@ namespace int rc = aio_return( me->cb.get() ); if( rc < 0 ) { + int errcode = aio_error( me->cb.get() ); Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, GetErrMsg( me->opcode ), XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + log->Error( FileMsg, GetErrMsg( me->opcode ), XrdSysE2T( errcode ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errcode ) ; QueueTask( error, 0, me->hosts, me->handler ); } else @@ -275,9 +274,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Close: file fd: %i %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } @@ -296,9 +293,7 @@ namespace XrdCl if( fstat( fd, &ssp ) == -1 ) { log->Error( FileMsg, "Stat: failed fd: %i %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } std::ostringstream data; @@ -332,9 +327,7 @@ namespace XrdCl if( ( read = pread( fd, buffer, size, offset ) ) == -1 ) { log->Error( FileMsg, "Read: failed %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } ChunkInfo *chunk = new ChunkInfo( offset, read, buffer ); @@ -351,8 +344,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Read: failed %s", XrdSysE2T( errno ) ); - return XRootDStatus( stError, errOSError, XProtocol::mapError( rc ), - XrdSysE2T( errno ) ); + return XRootDStatus( stError, errLocalError, errno ); } return XRootDStatus(); @@ -380,9 +372,7 @@ namespace XrdCl if( ret == -1 ) { log->Error( FileMsg, "ReadV: failed %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } VectorReadInfo *info = new VectorReadInfo(); @@ -418,9 +408,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Write: failed %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } offset += ret; @@ -438,8 +426,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Write: failed %s", XrdSysE2T( errno ) ); - return XRootDStatus( stError, errOSError, XProtocol::mapError( rc ), - XrdSysE2T( errno ) ); + return XRootDStatus( stError, errLocalError, errno ); } return XRootDStatus(); @@ -471,8 +458,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Sync: failed %s", XrdSysE2T( errno ) ); - return XRootDStatus( stError, errOSError, XProtocol::mapError( rc ), - XrdSysE2T( errno ) ); + return XRootDStatus( stError, errLocalError, errno ); } #endif return XRootDStatus(); @@ -489,9 +475,7 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "Truncate: failed, file descriptor: %i, %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } @@ -520,9 +504,7 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "VectorRead: failed, file descriptor: %i, %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } totalSize += bytesRead; @@ -554,9 +536,7 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "VectorWrite: failed, file descriptor: %i, %s", fd, XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } } @@ -597,9 +577,7 @@ namespace XrdCl { Log *log = DefaultEnv::GetLog(); log->Error( FileMsg, "WriteV: failed %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, errno ); return QueueTask( error, 0, handler ); } @@ -658,7 +636,7 @@ namespace XrdCl std::string name = std::get( *itr ); std::string value = std::get( *itr ); int err = xattr->Set( name.c_str(), value.c_str(), value.size(), 0, fd ); - XRootDStatus status = err ? XRootDStatus( stError, XProtocol::mapError( -errno ) ) : + XRootDStatus status = err < 0 ? XRootDStatus( stError, errLocalError, -err ) : XRootDStatus(); response.push_back( XAttrStatus( name, status ) ); @@ -696,7 +674,7 @@ namespace XrdCl if( ret >= 0 ) value.append( buffer.get(), ret ); else if( ret < 0 ) - status = XRootDStatus( stError, XProtocol::mapError( -ret ) ); + status = XRootDStatus( stError, errLocalError, -ret ); response.push_back( XAttr( *itr, value, status ) ); } @@ -722,8 +700,8 @@ namespace XrdCl { std::string name = *itr; int err = xattr->Del( name.c_str(), 0, fd ); - XRootDStatus status = err ? XRootDStatus( stError, XProtocol::mapError( -errno ) ) : - XRootDStatus(); + XRootDStatus status = err < 0 ? XRootDStatus( stError, errLocalError, -err ) : + XRootDStatus(); response.push_back( XAttrStatus( name, status ) ); } @@ -766,7 +744,7 @@ namespace XrdCl std::string value = ret >= 0 ? std::string( buffer.get(), ret ) : std::string(); XRootDStatus status = ret >= 0 ? XRootDStatus() : - XRootDStatus( stError, XProtocol::mapError( -ret ) ); + XRootDStatus( stError, errLocalError, -ret ); response.push_back( XAttr( name, value, status ) ); } xattr->Free( alist ); @@ -816,9 +794,7 @@ namespace XrdCl int rc = lstat( tmp.c_str(), &st ); if( rc == 0 ) break; if( errno != ENOENT ) - return XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + return XRootDStatus( stError, errLocalError, errno ); pos = path.rfind( '/', pos - 1 ); } @@ -829,9 +805,7 @@ namespace XrdCl if( mkdir( tmp.c_str(), 0755 ) ) { if( errno != EEXIST ) - return XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + return XRootDStatus( stError, errLocalError, errno ); } pos = path.find( '/', pos + 1 ); } From 16a28f7c4acf9b95dfb8c9477b5b95169a1100cf Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 13 Sep 2022 01:17:22 -0700 Subject: [PATCH 053/773] [Apps] Avoid SEGV when asking for help. --- src/XrdApps/XrdCks.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdApps/XrdCks.cc b/src/XrdApps/XrdCks.cc index 5a789c7ed4a..f8db00b495c 100644 --- a/src/XrdApps/XrdCks.cc +++ b/src/XrdApps/XrdCks.cc @@ -112,7 +112,7 @@ int main(int argc, char *argv[]) // Make sure the right number of arguments are here // - if (argc < 2) + if (argc <= 2) {if (argc > 1 && !strcmp(argv[1], "-h")) Usage(0); Usage(1); } From 81b12313c3785f6c1e995c4ff9f01aac2ce17258 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 13 Sep 2022 01:20:16 -0700 Subject: [PATCH 054/773] Update notes on xrdcks patch. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index be3e6093ed5..7b974164435 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -12,5 +12,7 @@ Prerelease Notes **Commit** 360e0d47 + **Minor bug fixes** + **[Apps]** Avoid SEGV when asking for help. + **Commit** 016a28f7 + **Miscellaneous** From 32a08d902685d82f3de77c34052e5c944b62dd48 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 14 Sep 2022 10:52:07 +0200 Subject: [PATCH 055/773] [XrdCl] Avoid redundant err msgs. --- src/XrdCl/XrdClCopy.cc | 4 ++-- src/XrdCl/XrdClFileSystem.cc | 10 ++++------ src/XrdCl/XrdClLocalFileHandler.cc | 6 ++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/XrdCl/XrdClCopy.cc b/src/XrdCl/XrdClCopy.cc index 1fd70446cac..a74b6f58413 100644 --- a/src/XrdCl/XrdClCopy.cc +++ b/src/XrdCl/XrdClCopy.cc @@ -683,7 +683,7 @@ int main( int argc, char **argv ) char *cwd = getcwd( buf, FILENAME_MAX ); if( !cwd ) { - XRootDStatus st( stError, XProtocol::mapError( errno ), errno, XrdSysE2T( errno ) ); + XRootDStatus st( stError, XProtocol::mapError( errno ), errno ); std::cerr << st.GetErrorMessage() << std::endl; return st.GetShellCode(); } @@ -806,7 +806,7 @@ int main( int argc, char **argv ) char *cwd = getcwd( buf, FILENAME_MAX ); if( !cwd ) { - XRootDStatus st( stError, XProtocol::mapError( errno ), errno, XrdSysE2T( errno ) ); + XRootDStatus st( stError, XProtocol::mapError( errno ), errno ); std::cerr << st.GetErrorMessage() << std::endl; return st.GetShellCode(); } diff --git a/src/XrdCl/XrdClFileSystem.cc b/src/XrdCl/XrdClFileSystem.cc index 00279d90f70..2d8002cdd3e 100644 --- a/src/XrdCl/XrdClFileSystem.cc +++ b/src/XrdCl/XrdClFileSystem.cc @@ -64,9 +64,8 @@ namespace if( stat( path.c_str(), &ssp ) == -1 ) { log->Error( FileMsg, "Stat: failed: %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, + XProtocol::mapError( errno ) ); return QueueTask( error, 0, handler ); } @@ -102,9 +101,8 @@ namespace if( unlink( path.c_str() ) ) { log->Error( FileMsg, "Rm: failed: %s", XrdSysE2T( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XRootDStatus *error = new XRootDStatus( stError, errLocalError, + XProtocol::mapError( errno ) ); return QueueTask( error, 0, handler ); } diff --git a/src/XrdCl/XrdClLocalFileHandler.cc b/src/XrdCl/XrdClLocalFileHandler.cc index 7399c8c42ed..ffed5e0e77c 100644 --- a/src/XrdCl/XrdClLocalFileHandler.cc +++ b/src/XrdCl/XrdClLocalFileHandler.cc @@ -867,8 +867,7 @@ namespace XrdCl XrdSysE2T( errno ) ); return XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XProtocol::mapError( errno ) ); } //--------------------------------------------------------------------- // Stat File and cache statInfo in openInfo @@ -878,8 +877,7 @@ namespace XrdCl { log->Error( FileMsg, "Open: stat failed." ); return XRootDStatus( stError, errLocalError, - XProtocol::mapError( errno ), - XrdSysE2T( errno ) ); + XProtocol::mapError( errno ) ); } std::ostringstream data; From 17ebef34f5accfd11154b0b245bf2f28f34042fe Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 14 Sep 2022 17:30:20 +0200 Subject: [PATCH 056/773] [XrdCl] Sanitize ls entry. --- src/XrdCl/XrdClXRootDResponses.hh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClXRootDResponses.hh b/src/XrdCl/XrdClXRootDResponses.hh index 7413a13477c..11fa1ca166c 100644 --- a/src/XrdCl/XrdClXRootDResponses.hh +++ b/src/XrdCl/XrdClXRootDResponses.hh @@ -663,7 +663,7 @@ namespace XrdCl const std::string &name, StatInfo *statInfo = 0): pHostAddress( hostAddress ), - pName( name ), + pName( SanitizeName( name ) ), pStatInfo( statInfo ) {} @@ -716,6 +716,15 @@ namespace XrdCl } private: + + inline static std::string SanitizeName( const std::string &name ) + { + const char *cstr = name.c_str(); + while( *cstr == '/' ) // the C string is guaranteed to end with '\0' + ++cstr; + return cstr; + } + std::string pHostAddress; std::string pName; StatInfo *pStatInfo; From 973869e332339ad63b466a7703a4ffc08ec1131b Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Fri, 16 Sep 2022 11:49:59 +0200 Subject: [PATCH 057/773] [CI] Publish ubuntu jammy artifacts. --- packaging/debian_scripts/publish_debian_cern.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/debian_scripts/publish_debian_cern.sh b/packaging/debian_scripts/publish_debian_cern.sh index 329bfb015e2..090f2056b31 100755 --- a/packaging/debian_scripts/publish_debian_cern.sh +++ b/packaging/debian_scripts/publish_debian_cern.sh @@ -10,7 +10,7 @@ set -e comp=$1 prefix=/eos/project/s/storage-ci/www/debian/xrootd -for dist in bionic focal; do +for dist in bionic focal jammy; do echo "Publishing for $dist"; path=$prefix/pool/$dist/$comp/x/xrootd/; mkdir -p $path; From 3d72fc759993fd7979f0735c52518a4d91a74a25 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Fri, 16 Sep 2022 15:30:13 +0200 Subject: [PATCH 058/773] [CI] Publish ubuntu jammy artifacts, part2. --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 256e2b4222f..a23eca3238e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -711,6 +711,7 @@ publish:debian: dependencies: - build:deb_ubuntu_bionic - build:deb_ubuntu_focal + - build:deb_ubuntu_jammy only: - master - /^stable-.*$/ @@ -761,6 +762,7 @@ publish:debian:release: dependencies: - release:deb_ubuntu_bionic - release:deb_ubuntu_focal + - release:deb_ubuntu_jammy only: - web except: From f05d89036864c648459ff80ef6d2e8bb9d7def5a Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Fri, 16 Sep 2022 16:45:58 +0200 Subject: [PATCH 059/773] [XrdCl] Update tests (errLocalError). --- 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 b0dc2719f7e..926f0d6debd 100644 --- a/tests/XrdClTests/FileCopyTest.cc +++ b/tests/XrdClTests/FileCopyTest.cc @@ -461,7 +461,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) CPPUNIT_ASSERT_XRDST_NOTOK( process16.Run( &progress16 ), errOperationInterrupted ); XrdCl::FileSystem localfs( "file://localhost" ); XrdCl::StatInfo *ptr = nullptr; - CPPUNIT_ASSERT_XRDST_NOTOK( localfs.Stat( "/data/tpcFile.dat", ptr ), XrdCl::errErrorResponse ); + CPPUNIT_ASSERT_XRDST_NOTOK( localfs.Stat( "/data/tpcFile.dat", ptr ), XrdCl::errLocalError ); //-------------------------------------------------------------------------- // Test --retry and --retry-policy From 639387b04b61cbe87311cb4bae3a31866efd0b15 Mon Sep 17 00:00:00 2001 From: Fabrizio Furano Date: Mon, 26 Sep 2022 14:36:34 +0200 Subject: [PATCH 060/773] XrdHttp: allow VO names with spaces and other quoted chars --- src/XrdHttp/XrdHttpReq.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 72c01e86051..9c753ae59b5 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -803,7 +803,11 @@ void XrdHttpReq::appendOpaque(XrdOucString &s, XrdSecEntity *secent, char *hash, if (secent->vorg) { s += "&xrdhttpvorg="; - s += secent->vorg; + char *s1 = quote(secent->vorg); + if (s1) { + s += s1; + free(s1); + } } if (secent->host) { From 449d3e93023381e7a717740f5f881a4ad4f1f432 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 26 Sep 2022 17:10:49 +0200 Subject: [PATCH 061/773] [XrdHttp] The server certificate is renewed by the Refresh thread of the XrdTlsContext object --- src/XrdTls/XrdTlsContext.cc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index e196bca33fd..ca56c7be606 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -864,12 +864,24 @@ void *XrdTlsContext::Session() // magic. For OpenSSL < 1.1, Two stores need to be set with the "set1" variant. // Newer version only require SSL_CTX_set1_cert_store() to be used. // - X509_STORE *newX509 = SSL_CTX_get_cert_store(pImpl->ctx); + //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); + SSL_CTX_set1_chain_cert_store(pImpl->ctx, newX509);*/ + //The above two macros actually do not replace the certificate that has + //to be used for that SSL session, so we will generate the session with the SSL_CTX * of + //the TlsContext created by Refresh() + pImpl->ctx = ctxnew->pImpl->ctx; + //In the destructor of XrdTlsContextImpl, SSL_CTX_Free() is + //called if ctx is != 0. As this new ctx is used by the 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 From 4bb71080ea2e1f4fafbc71085fc532fcc0347095 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 27 Sep 2022 10:53:00 +0200 Subject: [PATCH 062/773] [CI] Replace cc7 builds with centos 7, part 1. --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 97a11b2cc63..64dabbd87f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,12 +28,14 @@ jobs: cmake-centos7: runs-on: ubuntu-latest - container: gitlab-registry.cern.ch/linuxsupport/cc7-base: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 \ From a665900a8cc4c798707a2f62b807ea1db3ae8f0c Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 27 Sep 2022 01:55:37 -0700 Subject: [PATCH 063/773] [Server] Add O_RDWR open flag when creating file to avoid fs issue. --- src/XrdOss/XrdOssCreate.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdOss/XrdOssCreate.cc b/src/XrdOss/XrdOssCreate.cc index cecc881d0c2..7d955004938 100644 --- a/src/XrdOss/XrdOssCreate.cc +++ b/src/XrdOss/XrdOssCreate.cc @@ -293,7 +293,7 @@ int XrdOssSys::Alloc_Local(XrdOssCreateInfo &crInfo, XrdOucEnv &env) // Simply open the file in the local filesystem, creating it if need be. // - do {datfd = open(crInfo.Path, O_CREAT|O_TRUNC, crInfo.Amode);} + do {datfd = open(crInfo.Path, O_RDWR|O_CREAT|O_TRUNC, crInfo.Amode);} while(datfd < 0 && errno == EINTR); if (datfd < 0) return -errno; From e88fa2cb774f19a283e7e08ebdc82b1923669ba6 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 27 Sep 2022 01:59:16 -0700 Subject: [PATCH 064/773] Update notes on create fix for certain filesystems (e.g. Weka). --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 7b974164435..fad3d685e2a 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -14,5 +14,7 @@ Prerelease Notes + **Minor bug fixes** **[Apps]** Avoid SEGV when asking for help. **Commit** 016a28f7 + **[Server]** Add O_RDWR open flag when creating file to avoid fs issue + **Commit** 0a665900 + **Miscellaneous** From f3af43e9d371f01e1664cf0e0e07795454c3596e Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 27 Sep 2022 11:12:22 +0200 Subject: [PATCH 065/773] [CI] Replace cc7 builds with centos 7, part 2. --- .github/workflows/build.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64dabbd87f3..c28b36ef0df 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,12 +102,14 @@ jobs: cmake-centos7-updated-python: runs-on: ubuntu-latest - container: gitlab-registry.cern.ch/linuxsupport/cc7-base: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 \ @@ -168,13 +170,15 @@ jobs: cmake-centos7-python2: runs-on: ubuntu-latest - container: gitlab-registry.cern.ch/linuxsupport/cc7-base:latest + container: centos:7 steps: # python2-pip is broken on CentOS so can't upgrade pip or setuptools - 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 \ @@ -364,7 +368,7 @@ jobs: rpm-centos7: runs-on: ubuntu-latest - container: gitlab-registry.cern.ch/linuxsupport/cc7-base:latest + container: centos:7 steps: - name: Overwrite /etc/yum.repos.d/epel.repo to remove epel-source @@ -375,6 +379,8 @@ jobs: - 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 \ gcc-c++ \ rpm-build \ @@ -563,12 +569,14 @@ jobs: sdist-centos7: runs-on: ubuntu-latest - container: gitlab-registry.cern.ch/linuxsupport/cc7-base: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++ \ @@ -617,12 +625,14 @@ jobs: sdist-centos7-updated-python: runs-on: ubuntu-latest - container: gitlab-registry.cern.ch/linuxsupport/cc7-base: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++ \ From 4d7565a7a647110191fce79add640378fbcd1907 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 27 Sep 2022 11:35:33 +0200 Subject: [PATCH 066/773] [CI] Replace cc7 builds with centos 7, part 3. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c28b36ef0df..79565506497 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -373,14 +373,14 @@ jobs: steps: - name: Overwrite /etc/yum.repos.d/epel.repo to remove epel-source run: | + yum install -y epel-release centos-release-scl head -n -6 /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 -y epel-release centos-release-scl - yum clean all yum install --nogpg -y \ gcc-c++ \ rpm-build \ From 2f22f2d1d6fd40916859be8e51057b1d33e84540 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 27 Sep 2022 12:35:40 +0200 Subject: [PATCH 067/773] [CI] Replace cc7 builds with centos 7, part 4. --- .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 79565506497..0a4dc39e595 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -374,7 +374,7 @@ jobs: - name: Overwrite /etc/yum.repos.d/epel.repo to remove epel-source run: | yum install -y epel-release centos-release-scl - head -n -6 /etc/yum.repos.d/epel.repo > /tmp/epel.repo + 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 From bbb5feea26d641e70cf977be06714dffb7da22a3 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 28 Sep 2022 17:10:26 +0200 Subject: [PATCH 068/773] [XrdTls] The CRL refresh thread logic only starts if there is a CA file/directory set or if the server certificate has been changed --- src/XrdOuc/XrdOucUtils.cc | 12 ++++ src/XrdOuc/XrdOucUtils.hh | 2 + src/XrdTls/XrdTlsContext.cc | 107 +++++++++++++++++++----------------- src/XrdTls/XrdTlsContext.hh | 2 + 4 files changed, 72 insertions(+), 51 deletions(-) diff --git a/src/XrdOuc/XrdOucUtils.cc b/src/XrdOuc/XrdOucUtils.cc index c6b96b7eed9..13ca7b0fcb3 100644 --- a/src/XrdOuc/XrdOucUtils.cc +++ b/src/XrdOuc/XrdOucUtils.cc @@ -1345,5 +1345,17 @@ bool XrdOucUtils::PidFile(XrdSysError &eDest, const char *path) close(fd); return true; } +/******************************************************************************/ +/* getModificationTime */ +/******************************************************************************/ +int XrdOucUtils::getModificationTime(const char *path, time_t &modificationTime) { + struct stat buf; + int statRet = ::stat(path,&buf); + if(!statRet) { + modificationTime = buf.st_mtime; + } + return statRet; +} + #endif diff --git a/src/XrdOuc/XrdOucUtils.hh b/src/XrdOuc/XrdOucUtils.hh index 64a7340a389..0d34de582af 100644 --- a/src/XrdOuc/XrdOucUtils.hh +++ b/src/XrdOuc/XrdOucUtils.hh @@ -126,6 +126,8 @@ const char *ValPath(const char *path, mode_t allow, bool isdir); static bool PidFile(XrdSysError &eDest, const char *path); +static int getModificationTime(const char * path, time_t & modificationTime); + XrdOucUtils() {} ~XrdOucUtils() {} }; diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index ca56c7be606..5c2a8e2e88c 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -70,6 +70,7 @@ struct XrdTlsContextImpl short flushT; bool crlRunning; bool flsRunning; + time_t lastCertModTime = 0; }; /******************************************************************************/ @@ -118,38 +119,39 @@ do{ctxImpl->crlMutex.ReadLock(); // XrdSysTimer::Snooze(sleepTime); -// Check if this context is still alive. Generally, it never gets deleted. -// - ctxImpl->crlMutex.WriteLock(); - if (!ctxImpl->owner) break; - -// We clone the original, this will give us the latest crls (i.e. refreshed). -// We drop the lock while doing so as this may take a long time. This is -// completely safe to do because we implicitly own the implementation. -// - ctxImpl->crlMutex.UnLock(); - XrdTlsContext *newctx = ctxImpl->owner->Clone(); - -// Verify that the context was properly built -// - if (!newctx || !newctx->isOK()) - {XrdTls::Emsg("CrlRefresh:","Refresh of context failed!!!",false); - continue; - } + if (ctxImpl->owner->x509Verify() || ctxImpl->owner->newHostCertificateDetected()) { + // Check if this context is still alive. Generally, it never gets deleted. + // + ctxImpl->crlMutex.WriteLock(); + if (!ctxImpl->owner) break; -// OK, set the new context to be used next time Session() is called. -// - ctxImpl->crlMutex.WriteLock(); - doreplace = (ctxImpl->ctxnew != 0); - if (doreplace) delete ctxImpl->ctxnew; - ctxImpl->ctxnew = newctx; - ctxImpl->crlMutex.UnLock(); - -// Do some debugging -// - if (doreplace) {DBG_CTX("CRL refresh created replacement x509 store.");} - else {DBG_CTX("CRL refresh created new x509 store.");} + // We clone the original, this will give us the latest crls (i.e. refreshed). + // We drop the lock while doing so as this may take a long time. This is + // completely safe to do because we implicitly own the implementation. + // + ctxImpl->crlMutex.UnLock(); + XrdTlsContext *newctx = ctxImpl->owner->Clone(); + + // Verify that the context was properly built + // + if (!newctx || !newctx->isOK()) + {XrdTls::Emsg("CrlRefresh:","Refresh of context failed!!!",false); + continue; + } + + // OK, set the new context to be used next time Session() is called. + // + ctxImpl->crlMutex.WriteLock(); + doreplace = (ctxImpl->ctxnew != 0); + if (doreplace) delete ctxImpl->ctxnew; + ctxImpl->ctxnew = newctx; + ctxImpl->crlMutex.UnLock(); + // Do some debugging + // + if (doreplace) {DBG_CTX("CRL refresh created replacement x509 store.");} + else {DBG_CTX("CRL refresh created new x509 store.");} + } } while(true); // If we are here the context that started us has gone away and we are done @@ -616,7 +618,11 @@ XrdTlsContext::XrdTlsContext(const char *cert, const char *key, // Copy parameters to out parm structure. // - if (cert) pImpl->Parm.cert = cert; + if (cert) { + pImpl->Parm.cert = cert; + //This call should not fail as a stat is already performed in the call of VerPaths() above + XrdOucUtils::getModificationTime(pImpl->Parm.cert.c_str(),pImpl->lastCertModTime); + } if (key) pImpl->Parm.pkey = key; if (caDir) pImpl->Parm.cadir = caDir; if (caFile) pImpl->Parm.cafile = caFile; @@ -1001,33 +1007,15 @@ bool XrdTlsContext::SetCrlRefresh(int refsec) pthread_t tid; int rc; -// If the value is zero, then the caller want to stop refreshing. -// - if (!refsec) - {pImpl->crlMutex.WriteLock(); - pImpl->Parm.crlRT = 0; - pImpl->crlMutex.UnLock(); - return true; - } - -// If it's negative, use the curren setting +// If it's negative or equal to 0, use the current setting // - if (refsec < 0) + if (refsec <= 0) {pImpl->crlMutex.WriteLock(); refsec = pImpl->Parm.crlRT; pImpl->crlMutex.UnLock(); if (!refsec) refsec = 8*60*60; } - -// Check if there is anything that is refreshable -// - if (!x509Verify()) - {XrdTls::Emsg("CrlRefresh:", - "No cert information exists to refresh!", false); - return false; - } - // Make sure this is at least 60 seconds between refreshes // // if (refsec < 60) refsec = 60; @@ -1072,3 +1060,20 @@ bool XrdTlsContext::x509Verify() { return !(pImpl->Parm.cadir.empty()) || !(pImpl->Parm.cafile.empty()); } + +bool XrdTlsContext::newHostCertificateDetected() { + const std::string certPath = pImpl->Parm.cert; + if(certPath.empty()) { + //No certificate provided, should not happen though + return false; + } + time_t modificationTime; + if(!XrdOucUtils::getModificationTime(certPath.c_str(),modificationTime)){ + if (pImpl->lastCertModTime != modificationTime) { + //The certificate file has changed + pImpl->lastCertModTime = modificationTime; + return true; + } + } + return false; +} \ No newline at end of file diff --git a/src/XrdTls/XrdTlsContext.hh b/src/XrdTls/XrdTlsContext.hh index 6eca044c333..64ffcbcc9f1 100644 --- a/src/XrdTls/XrdTlsContext.hh +++ b/src/XrdTls/XrdTlsContext.hh @@ -178,6 +178,8 @@ void SetDefaultCiphers(const char *ciphers); bool x509Verify(); + bool newHostCertificateDetected(); + //------------------------------------------------------------------------ //! Constructor. Note that you should use isOK() to determine if construction //! was successful. A false return indicates failure. From 8847a1db561e234607794b5119424ea2bb2d85bd Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 29 Sep 2022 12:44:02 +0200 Subject: [PATCH 069/773] [CMake] add ENABLE_ switch for scitokens and macaroons, closes #1728. --- cmake/XRootDDefaults.cmake | 2 ++ cmake/XRootDFindLibs.cmake | 48 +++++++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index b2d9d951838..bbee8a275b5 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -28,5 +28,7 @@ option( ENABLE_XRDEC "Enable erasure coding component." option( ENABLE_ASAN "Enable adress sanitizer." FALSE ) option( ENABLE_TSAN "Enable thread sanitizer." FALSE ) option( ENABLE_XRDCLHTTP "Enable xrdcl-http plugin." TRUE ) +option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE ) +option( ENABLE_MACAROONS "Enable Macaroons plugin." TRUE ) option( FORCE_ENABLED "Fail build if enabled components cannot be built." FALSE ) define_default( XRD_PYTHON_REQ_VERSION 3 ) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 8a06ef7919e..333a5d06865 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -119,25 +119,41 @@ check_function_exists( curl_multi_wait HAVE_CURL_MULTI_WAIT ) compiler_define_if_found( HAVE_CURL_MULTI_WAIT HAVE_CURL_MULTI_WAIT ) endif() -find_package( Macaroons ) - -if( NOT MacOSX ) - include(FindPkgConfig REQUIRED) - pkg_check_modules(JSON json-c) - pkg_check_modules(UUID REQUIRED uuid) +if( ENABLE_MACAROONS ) + if( FORCE_ENABLED ) + find_package( Macaroons REQUIRED ) + else() + find_package( Macaroons ) + endif() + if( NOT MacOSX ) + 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 ) + set( BUILD_MACAROONS TRUE ) + else() + set( BUILD_MACAROONS FALSE ) + endif() endif() -if( MACAROONS_FOUND AND JSON_FOUND AND UUID_FOUND ) - set( BUILD_MACAROONS TRUE ) -else() - set( BUILD_MACAROONS FALSE ) -endif() -find_package( SciTokensCpp ) -if( SCITOKENSCPP_FOUND ) - set( BUILD_SCITOKENS TRUE ) -else() - set( BUILD_SCITOKENS FALSE ) +if( ENABLE_SCITOKENS ) + if( FORCE_ENABLED ) + find_package( SciTokensCpp REQUIRED ) + else() + find_package( SciTokensCpp ) + endif() + if( SCITOKENSCPP_FOUND ) + set( BUILD_SCITOKENS TRUE ) + else() + set( BUILD_SCITOKENS FALSE ) + endif() endif() if( ENABLE_XRDEC ) From 19038b5ac2d97649a182f44124a5efa299c64b73 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 29 Sep 2022 14:02:35 +0200 Subject: [PATCH 070/773] [XrdTls] The start of the CRLRefresh thread is now triggered in the constructor of the XrdTlsContext class A boolean flag has been added to the constructor and the Clone() method of the XrdTlsContext class. If set to true, the TLS context created or cloned will run the CRLRefresh thread after having been succesfully initialized. --- src/XrdHttp/XrdHttpProtocol.cc | 7 +------ src/XrdTls/XrdTlsContext.cc | 11 +++++++---- src/XrdTls/XrdTlsContext.hh | 4 ++-- src/XrdXrootd/XrdXrootdConfig.cc | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index c0b3569f6c6..316e1419a65 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1133,11 +1133,6 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { eDest.Say("------ HTTPS initialization ", how); if (NoGo) return NoGo; -// Turn on the refreshing -// - if (!NoGo && xrdctx->x509Verify() && !(xrdctx->SetCrlRefresh())) - eDest.Say("Config warning: CRL refreshing could not be enabled!"); - // We can now load all the external handlers // if (LoadExtHandler(extHIVec, ConfigFN, *myEnv)) return 1; @@ -1696,7 +1691,7 @@ bool XrdHttpProtocol::InitTLS() { // if (sslverifydepth > 255) sslverifydepth = 255; opts = TLS_SET_VDEPTH(opts, sslverifydepth); - xrdctx = new XrdTlsContext(sslcert,sslkey,sslcadir,sslcafile,opts,&eMsg); + xrdctx = new XrdTlsContext(sslcert,sslkey,sslcadir,sslcafile,opts,&eMsg,true); // Make sure the context was created // diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 5c2a8e2e88c..8c30689b9b3 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -540,7 +540,7 @@ int VerCB(int aOK, X509_STORE_CTX *x509P) XrdTlsContext::XrdTlsContext(const char *cert, const char *key, const char *caDir, const char *caFile, - uint64_t opts, std::string *eMsg) + uint64_t opts, std::string *eMsg,const bool startCRLRefreshThread) : pImpl( new XrdTlsContextImpl(this) ) { class ctx_helper @@ -724,8 +724,11 @@ XrdTlsContext::XrdTlsContext(const char *cert, const char *key, if (SSL_CTX_check_private_key(pImpl->ctx) != 1 ) FATAL_SSL("Unable to create TLS context; cert-key mismatch."); -// All went well, so keep the context. +// All went well, start the CRL refresh thread and keep the context. // + if(startCRLRefreshThread) { + SetCrlRefresh(); + } ctx_tracker.Keep(); } @@ -749,7 +752,7 @@ XrdTlsContext::~XrdTlsContext() /* C l o n e */ /******************************************************************************/ -XrdTlsContext *XrdTlsContext::Clone(bool full) +XrdTlsContext *XrdTlsContext::Clone(bool full,bool startCRLRefresh) { XrdTlsContext::CTX_Params &my = pImpl->Parm; const char *cert = (my.cert.size() ? my.cert.c_str() : 0); @@ -763,7 +766,7 @@ XrdTlsContext *XrdTlsContext::Clone(bool full) // Cloning simply means getting a object with the old parameters. // - XrdTlsContext *xtc = new XrdTlsContext(cert, pkey, caD, caF, my.opts); + XrdTlsContext *xtc = new XrdTlsContext(cert, pkey, caD, caF, my.opts,nullptr,startCRLRefresh); // Verify that the context was built // diff --git a/src/XrdTls/XrdTlsContext.hh b/src/XrdTls/XrdTlsContext.hh index 64ffcbcc9f1..d5e31b08f1c 100644 --- a/src/XrdTls/XrdTlsContext.hh +++ b/src/XrdTls/XrdTlsContext.hh @@ -52,7 +52,7 @@ public: //! the session cache is set to off with no identifier. //------------------------------------------------------------------------ -XrdTlsContext *Clone(bool full=true); +XrdTlsContext *Clone(bool full=true, bool startCRLRefresh = false); //------------------------------------------------------------------------ //! Get the underlying context (should not be used). @@ -238,7 +238,7 @@ static const uint64_t artON = 0x0000002000000000; //!< Auto retry Handshake XrdTlsContext(const char *cert=0, const char *key=0, const char *cadir=0, const char *cafile=0, - uint64_t opts=0, std::string *eMsg=0); + uint64_t opts=0, std::string *eMsg=0,const bool startCRLRefreshThread = false); //------------------------------------------------------------------------ //! Destructor diff --git a/src/XrdXrootd/XrdXrootdConfig.cc b/src/XrdXrootd/XrdXrootdConfig.cc index b0ee4b8089c..08746b24164 100644 --- a/src/XrdXrootd/XrdXrootdConfig.cc +++ b/src/XrdXrootd/XrdXrootdConfig.cc @@ -576,7 +576,7 @@ int XrdXrootdProtocol::Config(const char *ConfigFN) // context must be of the non-verified kind as we don't accept certs. // if (!NoGo && tlsCtx) - {tlsCtx = tlsCtx->Clone(false); + {tlsCtx = tlsCtx->Clone(false,true); if (!tlsCtx) {eDest.Say("Config failure: unable to setup TLS for protocol!"); NoGo = 1; From e4187ee676bb3515ccac08e8c28f7b0a6b839e15 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 29 Sep 2022 14:27:16 +0200 Subject: [PATCH 071/773] [XrdTlsContext] Free the SSL Context when a new one is going to be used to generate the session If that SSL Context was used to generate a Session, it will only decrease the reference counter, not delete it --- src/XrdTls/XrdTlsContext.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 5c2a8e2e88c..8f24b7b31f6 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -878,8 +878,11 @@ void *XrdTlsContext::Session() SSL_CTX_set1_verify_cert_store(pImpl->ctx, newX509); SSL_CTX_set1_chain_cert_store(pImpl->ctx, newX509);*/ //The above two macros actually do not replace the certificate that has - //to be used for that SSL session, so we will generate the session with the SSL_CTX * of + //to be used for that SSL session, so we will create the session with the SSL_CTX * of //the TlsContext created by Refresh() + //First, free the current SSL_CTX, if it is used by any transfer, it will just decrease + //the reference counter of it. There is therefore no risk of double free... + SSL_CTX_free(pImpl->ctx); pImpl->ctx = ctxnew->pImpl->ctx; //In the destructor of XrdTlsContextImpl, SSL_CTX_Free() is //called if ctx is != 0. As this new ctx is used by the session From b180a248331934ae33607a2d3a779abf936c7d6d Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 29 Sep 2022 15:19:51 +0200 Subject: [PATCH 072/773] [XrdTlsContext] Remove the flag to start the CRL refresh thread from the constructor This flag was moved with the other options given to the constructor --- src/XrdHttp/XrdHttpProtocol.cc | 4 ++-- src/XrdTls/XrdTlsContext.cc | 12 +++++++++--- src/XrdTls/XrdTlsContext.hh | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 316e1419a65..6a91139b34d 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1685,13 +1685,13 @@ bool XrdHttpProtocol::InitTLS() { std::string eMsg; uint64_t opts = XrdTlsContext::servr | XrdTlsContext::logVF | - XrdTlsContext::artON; + XrdTlsContext::artON | XrdTlsContext::scRefr; // Create a new TLS context // if (sslverifydepth > 255) sslverifydepth = 255; opts = TLS_SET_VDEPTH(opts, sslverifydepth); - xrdctx = new XrdTlsContext(sslcert,sslkey,sslcadir,sslcafile,opts,&eMsg,true); + xrdctx = new XrdTlsContext(sslcert,sslkey,sslcadir,sslcafile,opts,&eMsg); // Make sure the context was created // diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 8c30689b9b3..e3a5853ddf0 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -540,7 +540,7 @@ int VerCB(int aOK, X509_STORE_CTX *x509P) XrdTlsContext::XrdTlsContext(const char *cert, const char *key, const char *caDir, const char *caFile, - uint64_t opts, std::string *eMsg,const bool startCRLRefreshThread) + uint64_t opts, std::string *eMsg) : pImpl( new XrdTlsContextImpl(this) ) { class ctx_helper @@ -726,7 +726,7 @@ XrdTlsContext::XrdTlsContext(const char *cert, const char *key, // All went well, start the CRL refresh thread and keep the context. // - if(startCRLRefreshThread) { + if(opts & scRefr) { SetCrlRefresh(); } ctx_tracker.Keep(); @@ -766,7 +766,13 @@ XrdTlsContext *XrdTlsContext::Clone(bool full,bool startCRLRefresh) // Cloning simply means getting a object with the old parameters. // - XrdTlsContext *xtc = new XrdTlsContext(cert, pkey, caD, caF, my.opts,nullptr,startCRLRefresh); + uint64_t myOpts = my.opts; + if(startCRLRefresh){ + myOpts |= XrdTlsContext::scRefr; + } else { + myOpts &= ~XrdTlsContext::scRefr; + } + XrdTlsContext *xtc = new XrdTlsContext(cert, pkey, caD, caF, myOpts,nullptr); // Verify that the context was built // diff --git a/src/XrdTls/XrdTlsContext.hh b/src/XrdTls/XrdTlsContext.hh index d5e31b08f1c..026a658fbb4 100644 --- a/src/XrdTls/XrdTlsContext.hh +++ b/src/XrdTls/XrdTlsContext.hh @@ -130,6 +130,7 @@ static const int scNone = 0x00000000; //!< Do not change any option settings static const int scOff = 0x00010000; //!< Turn off cache static const int scSrvr = 0x00020000; //!< Turn on cache server mode (default) static const int scClnt = 0x00040000; //!< Turn on cache client mode +static const int scRefr = 0x20000000; //!< Turn on the CRL refresh thread static const int scKeep = 0x40000000; //!< Info: TLS-controlled flush disabled static const int scIdErr= 0x80000000; //!< Info: Id not set, is too long static const int scFMax = 0x00007fff; //!< Maximum flush interval in seconds @@ -238,7 +239,7 @@ static const uint64_t artON = 0x0000002000000000; //!< Auto retry Handshake XrdTlsContext(const char *cert=0, const char *key=0, const char *cadir=0, const char *cafile=0, - uint64_t opts=0, std::string *eMsg=0,const bool startCRLRefreshThread = false); + uint64_t opts=0, std::string *eMsg=0); //------------------------------------------------------------------------ //! Destructor From 87da7f2fa93258abf1cff771b4854c49dcd298d6 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 29 Sep 2022 16:56:51 +0200 Subject: [PATCH 073/773] [XrdTlsContext] Changed the bit set for the activation of the Refresh thread --- src/XrdHttp/XrdHttpProtocol.cc | 2 +- src/XrdTls/XrdTlsContext.cc | 14 +++++++------- src/XrdTls/XrdTlsContext.hh | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 6a91139b34d..f41c3acb75b 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1685,7 +1685,7 @@ bool XrdHttpProtocol::InitTLS() { std::string eMsg; uint64_t opts = XrdTlsContext::servr | XrdTlsContext::logVF | - XrdTlsContext::artON | XrdTlsContext::scRefr; + XrdTlsContext::artON | XrdTlsContext::rfCRL; // Create a new TLS context // diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index f841916c01d..9ab4630db50 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -726,7 +726,7 @@ XrdTlsContext::XrdTlsContext(const char *cert, const char *key, // All went well, start the CRL refresh thread and keep the context. // - if(opts & scRefr) { + if(opts & rfCRL) { SetCrlRefresh(); } ctx_tracker.Keep(); @@ -767,12 +767,12 @@ XrdTlsContext *XrdTlsContext::Clone(bool full,bool startCRLRefresh) // Cloning simply means getting a object with the old parameters. // uint64_t myOpts = my.opts; - if(startCRLRefresh){ - myOpts |= XrdTlsContext::scRefr; - } else { - myOpts &= ~XrdTlsContext::scRefr; - } - XrdTlsContext *xtc = new XrdTlsContext(cert, pkey, caD, caF, myOpts,nullptr); + if(startCRLRefresh){ + myOpts |= XrdTlsContext::rfCRL; + } else { + myOpts &= ~XrdTlsContext::rfCRL; + } + XrdTlsContext *xtc = new XrdTlsContext(cert, pkey, caD, caF, myOpts); // Verify that the context was built // diff --git a/src/XrdTls/XrdTlsContext.hh b/src/XrdTls/XrdTlsContext.hh index 026a658fbb4..9fef4fff532 100644 --- a/src/XrdTls/XrdTlsContext.hh +++ b/src/XrdTls/XrdTlsContext.hh @@ -130,7 +130,6 @@ static const int scNone = 0x00000000; //!< Do not change any option settings static const int scOff = 0x00010000; //!< Turn off cache static const int scSrvr = 0x00020000; //!< Turn on cache server mode (default) static const int scClnt = 0x00040000; //!< Turn on cache client mode -static const int scRefr = 0x20000000; //!< Turn on the CRL refresh thread static const int scKeep = 0x40000000; //!< Info: TLS-controlled flush disabled static const int scIdErr= 0x80000000; //!< Info: Id not set, is too long static const int scFMax = 0x00007fff; //!< Maximum flush interval in seconds @@ -231,6 +230,7 @@ static const uint64_t logVF = 0x0000000800000000; //!< Log verify failures static const uint64_t servr = 0x0000000400000000; //!< This is a server context static const uint64_t dnsok = 0x0000000200000000; //!< Trust DNS for host name static const uint64_t nopxy = 0x0000000100000000; //!< Do not allow proxy certs +static const uint64_t rfCRL = 0x0000004000000000; //!< Turn on the CRL refresh thread static const uint64_t crlON = 0x0000008000000000; //!< Enables crl checking static const uint64_t crlFC = 0x000000C000000000; //!< Full crl chain checking static const uint64_t crlRF = 0x000000003fff0000; //!< Init crl refresh in Min From 9a48ecf4915079068ba1f438febba08150ecf3f7 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 29 Sep 2022 17:13:54 +0200 Subject: [PATCH 074/773] [XrdTlsSocket] Shutdown the socket if a SSL error happens when trying to accept a connection If we do not do that, next time a XROOTD client using the link that got an SSL error will see a SSL Socket error and will have to retry --- src/XrdTls/XrdTlsSocket.cc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/XrdTls/XrdTlsSocket.cc b/src/XrdTls/XrdTlsSocket.cc index 8f5d566db2f..f4b36a91be6 100644 --- a/src/XrdTls/XrdTlsSocket.cc +++ b/src/XrdTls/XrdTlsSocket.cc @@ -216,8 +216,15 @@ do{if ((rc = SSL_accept( pImpl->ssl )) > 0) // Check why we did not succeed. We may be able to recover. // - if (ssler != SSL_ERROR_WANT_READ && ssler != SSL_ERROR_WANT_WRITE) - {aOK = false; break;} + if (ssler != SSL_ERROR_WANT_READ && ssler != SSL_ERROR_WANT_WRITE) { + if(ssler == SSL_ERROR_SSL){ + //In the case the accept does have an error related to OpenSSL, + //shutdown the TLSSocket in case the link associated to that connection + //is re-used + Shutdown(); + } + aOK = false; break; + } if (pImpl->hsNoBlock) return XrdTls::ssl2RC(ssler); From 838fc08b285c7760db6df672ebadf651197b85af Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 29 Sep 2022 10:42:59 -0700 Subject: [PATCH 075/773] Update notes on recet rash of comits. --- docs/PreReleaseNotes.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index fad3d685e2a..8f72cfb1d2a 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -16,5 +16,27 @@ Prerelease Notes **Commit** 016a28f7 **[Server]** Add O_RDWR open flag when creating file to avoid fs issue **Commit** 0a665900 + **[HTTP]** allow VO names with spaces and other quoted chars + **Commit** 639387b0 504dfb78 + **Miscellaneous** + **[TLS]** Shutdown the socket if a SSL error happens during an accept + **Commit** 9a48ecf4 ebbe8397 + **[TLS]** Trigger CRL refresh thread via the constructor. + **Commit** 19038b5a b180a248 f1acf6d4 87da7f2f f1dfad02 + **[TLS]** Free current context when a new context is generated. + **Commit** e4187ee6 411d3eb4 + **[Cmake]** add ENABLE_ switch for scitokens and macaroons, closes #1728. + **Commit** 8847a1db + **[TLS]** he CRL refresh thread logic only starts when there is a need for it. + **Commit** bbb5feea b11f8c00 + **[TLS]** The server certificate is also renewed by the Refresh thread + **Commit** 449d3e93 ff90fe2f + **[CI]** Replace cc7 builds with centos 7, part 4. + **Commit** 2f22f2d1 + **[CI]** Replace cc7 builds with centos 7, part 3. + **Commit** 4d7565a7 + **[CI]** Replace cc7 builds with centos 7, part 2. + **Commit** f3af43e9 + **[CI]** Replace cc7 builds with centos 7, part 1. + **Commit** 4bb71080 From d9b8914b835cb2a2442e48908bea9a4af27818dd Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 4 Oct 2022 11:28:12 +0200 Subject: [PATCH 076/773] [XrdTls] The tlsca 'refresh' directive in the configuration file is now taken into account for both XrootD and HTTP TLS context --- src/Xrd/XrdConfig.cc | 4 +++- src/XrdHttp/XrdHttpProtocol.cc | 10 +++++++++- src/XrdHttp/XrdHttpProtocol.hh | 3 +++ src/XrdTls/XrdTlsContext.cc | 8 +++++--- src/XrdTls/XrdTlsContext.hh | 7 +++++-- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index 58ccfad06cb..f1d13566509 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include "XrdVersion.hh" @@ -2450,7 +2452,7 @@ int XrdConfig::xtlsca(XrdSysError *eDest, XrdOucStream &Config) } else if (!strcmp(kword, "refresh")) {if (XrdOuca2x::a2tm(*eDest, "tlsca refresh interval", - val, &rt)) return 1; + val, &rt,1,std::min(int((XrdTlsContext::crlRF >> XrdTlsContext::crlRS) * 60),std::numeric_limits::max()))) return 1; if (rt < 60) rt = 60; else if (rt % 60) rt += 60; rt = rt/60; diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index f41c3acb75b..f7ee7b2cc91 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -75,6 +75,7 @@ char *XrdHttpProtocol::Port_str = 0; char *XrdHttpProtocol::sslcert = 0; char *XrdHttpProtocol::sslkey = 0; char *XrdHttpProtocol::sslcadir = 0; +int XrdHttpProtocol::crlRefIntervalSec = XrdTlsContext::DEFAULT_CRL_REF_INT_SEC; char *XrdHttpProtocol::sslcipherfilter = 0; char *XrdHttpProtocol::listredir = 0; bool XrdHttpProtocol::listdeny = false; @@ -1087,7 +1088,7 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { // if (httpsmode == hsmAuto && xrdctx) {const XrdTlsContext::CTX_Params *cP = xrdctx->GetParams(); - const char *what1 = 0, *what2 = 0; + const char *what1 = 0, *what2 = 0, *what3 = 0; if (!sslcert && cP->cert.size()) {sslcert = strdup(cP->cert.c_str()); @@ -1103,8 +1104,13 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { what2 = (what2 ? "xrd.tlsca to supply 'cadir' and 'cafile'." : "xrd.tlsca to supply 'cafile'."); } + if(cP->crlRT != XrdTlsContext::DEFAULT_CRL_REF_INT_SEC) { + crlRefIntervalSec = cP->crlRT; + what3 = "xrd.tlsca to supply 'refresh' interval."; + } if (!httpsspec && what1) eDest.Say("Config Using ", what1); if (!httpsspec && what2) eDest.Say("Config Using ", what2); + if (!httpsspec && what3) eDest.Say("Config Using ", what3); } // If a gridmap or secxtractor is present then we must be able to verify certs @@ -1691,6 +1697,8 @@ bool XrdHttpProtocol::InitTLS() { // if (sslverifydepth > 255) sslverifydepth = 255; opts = TLS_SET_VDEPTH(opts, sslverifydepth); + //TLS_SET_REFINT will set the refresh interval in minutes, hence the division by 60 + opts = TLS_SET_REFINT(opts, crlRefIntervalSec/60); xrdctx = new XrdTlsContext(sslcert,sslkey,sslcadir,sslcafile,opts,&eMsg); // Make sure the context was created diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index 51bea7e52de..51d31c34a4a 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -365,6 +365,9 @@ protected: /// OpenSSL stuff static char *sslcert, *sslkey, *sslcadir, *sslcafile, *sslcipherfilter; + /// CRL thread refresh interval + static int crlRefIntervalSec; + /// Gridmap file location. The same used by XrdSecGsi static char *gridmap;// [s] gridmap file [/etc/grid-security/gridmap] static bool isRequiredGridmap; // If true treat gridmap errors as fatal diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 9ab4630db50..0a9454f9568 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -627,8 +627,10 @@ XrdTlsContext::XrdTlsContext(const char *cert, const char *key, if (caDir) pImpl->Parm.cadir = caDir; if (caFile) pImpl->Parm.cafile = caFile; pImpl->Parm.opts = opts; - if (opts & crlRF) - pImpl->Parm.crlRT = static_cast((opts & crlRF)>>crlRS); + if (opts & crlRF) { + // What we store in crlRF is the time in minutes, convert it back to seconds + pImpl->Parm.crlRT = static_cast((opts & crlRF) >> crlRS) * 60; + } // Get the correct method to use for TLS and check if successful create a // server context that uses the method. @@ -1025,7 +1027,7 @@ bool XrdTlsContext::SetCrlRefresh(int refsec) {pImpl->crlMutex.WriteLock(); refsec = pImpl->Parm.crlRT; pImpl->crlMutex.UnLock(); - if (!refsec) refsec = 8*60*60; + if (!refsec) refsec = XrdTlsContext::DEFAULT_CRL_REF_INT_SEC; } // Make sure this is at least 60 seconds between refreshes diff --git a/src/XrdTls/XrdTlsContext.hh b/src/XrdTls/XrdTlsContext.hh index 9fef4fff532..2b510378fb8 100644 --- a/src/XrdTls/XrdTlsContext.hh +++ b/src/XrdTls/XrdTlsContext.hh @@ -62,6 +62,9 @@ XrdTlsContext *Clone(bool full=true, bool startCRLRefresh = false); void *Context(); +//! Default CRL refresh interval in seconds +static const int DEFAULT_CRL_REF_INT_SEC = 8 * 60 * 60; + //------------------------------------------------------------------------ //! Get parameters used to create the context. //! @@ -77,7 +80,7 @@ struct CTX_Params int crlRT; //!< crl refresh interval time in seconds int rsvd; - CTX_Params() : opts(0), crlRT(8*60*60), rsvd(0) {} + CTX_Params() : opts(0), crlRT(DEFAULT_CRL_REF_INT_SEC), rsvd(0) {} ~CTX_Params() {} }; @@ -233,7 +236,7 @@ static const uint64_t nopxy = 0x0000000100000000; //!< Do not allow proxy certs static const uint64_t rfCRL = 0x0000004000000000; //!< Turn on the CRL refresh thread static const uint64_t crlON = 0x0000008000000000; //!< Enables crl checking static const uint64_t crlFC = 0x000000C000000000; //!< Full crl chain checking -static const uint64_t crlRF = 0x000000003fff0000; //!< Init crl refresh in Min +static const uint64_t crlRF = 0x00000000ffff0000; //!< Mask to isolate crl refresh in min static const int crlRS = 16; //!< Bits to shift vdept static const uint64_t artON = 0x0000002000000000; //!< Auto retry Handshake From cc1a6adc24dc655502300264c80c9bfdbbe7c332 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 6 Oct 2022 11:07:09 +0200 Subject: [PATCH 077/773] [XrdCl] LocalFileHandler: be more robust in case PostMaster initialization failed, closes #1797. --- src/XrdCl/XrdClLocalFileHandler.cc | 7 +++---- src/XrdCl/XrdClLocalFileHandler.hh | 5 ----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/XrdCl/XrdClLocalFileHandler.cc b/src/XrdCl/XrdClLocalFileHandler.cc index ffed5e0e77c..e25d8f2a2ba 100644 --- a/src/XrdCl/XrdClLocalFileHandler.cc +++ b/src/XrdCl/XrdClLocalFileHandler.cc @@ -201,7 +201,7 @@ namespace // and return there is no need to execute this in the thread-pool SyncResponseHandler *syncHandler = dynamic_cast( handler ); - if( syncHandler ) + if( syncHandler || DefaultEnv::GetPostMaster() == nullptr ) { syncHandler->HandleResponse( status, resp ); } @@ -230,7 +230,6 @@ namespace XrdCl LocalFileHandler::LocalFileHandler() : fd( -1 ) { - jmngr = DefaultEnv::GetPostMaster()->GetJobManager(); } //------------------------------------------------------------------------ @@ -767,7 +766,7 @@ namespace XrdCl // and return there is no need to execute this in the thread-pool SyncResponseHandler *syncHandler = dynamic_cast( handler ); - if( syncHandler ) + if( syncHandler || DefaultEnv::GetPostMaster() == nullptr ) { syncHandler->HandleResponse( st, resp ); return XRootDStatus(); @@ -775,7 +774,7 @@ namespace XrdCl HostList *hosts = pHostList.empty() ? 0 : new HostList( pHostList ); LocalFileTask *task = new LocalFileTask( st, resp, hosts, handler ); - jmngr->QueueJob( task ); + DefaultEnv::GetPostMaster()->GetJobManager()->QueueJob( task ); return XRootDStatus(); } diff --git a/src/XrdCl/XrdClLocalFileHandler.hh b/src/XrdCl/XrdClLocalFileHandler.hh index 461433a9475..2fc03568666 100644 --- a/src/XrdCl/XrdClLocalFileHandler.hh +++ b/src/XrdCl/XrdClLocalFileHandler.hh @@ -330,11 +330,6 @@ namespace XrdCl char *body, ResponseHandler *handler ); - //--------------------------------------------------------------------- - // Receives LocalFileTasks to handle them async - //--------------------------------------------------------------------- - JobManager *jmngr; - //--------------------------------------------------------------------- // Internal filedescriptor, which is used by all operations after open //--------------------------------------------------------------------- From fdfe7ef58874cbe32e4357ffe05a7d2d002c7bda Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Mon, 10 Oct 2022 13:57:29 +0200 Subject: [PATCH 078/773] [XrdCl] xrdcp: pass authz token with stat preceding copy. --- src/XrdCl/XrdClCopy.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClCopy.cc b/src/XrdCl/XrdClCopy.cc index a74b6f58413..4a8360eed2f 100644 --- a/src/XrdCl/XrdClCopy.cc +++ b/src/XrdCl/XrdClCopy.cc @@ -710,7 +710,7 @@ int main( int argc, char **argv ) URL target( dest ); FileSystem fs( target ); StatInfo *statInfo = 0; - XRootDStatus st = fs.Stat( target.GetPath(), statInfo ); + XRootDStatus st = fs.Stat( target.GetPathWithParams(), statInfo ); if( st.IsOK() ) { if( statInfo->TestFlags( StatInfo::IsDir ) ) @@ -723,6 +723,12 @@ int main( int argc, char **argv ) if( config.dstFile->Path[n-1] == '/' ) targetIsDir = true; } + else if( st.errNo == kXR_NotAuthorized ) + { + log->Error( AppMsg, "%s (destination)", st.ToString().c_str() ); + std::cerr << st.ToStr() << std::endl; + return st.GetShellCode(); + } delete statInfo; } From e0501bb638d92d1b89deec6b34e63f928a6f37ae Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 10 Oct 2022 17:17:38 +0200 Subject: [PATCH 079/773] [XrdHttp + XrdHttpExtHandler] Fixes a segmentation fault happening when a client sends a first line starting with a space --- src/XrdHttp/XrdHttpExtHandler.cc | 3 ++- src/XrdHttp/XrdHttpReq.cc | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/XrdHttp/XrdHttpExtHandler.cc b/src/XrdHttp/XrdHttpExtHandler.cc index 10195aa8815..c409b0ea7ae 100644 --- a/src/XrdHttp/XrdHttpExtHandler.cc +++ b/src/XrdHttp/XrdHttpExtHandler.cc @@ -90,7 +90,8 @@ verb(req->requestverb), headers(req->allheaders) { int envlen = 0; headers["xrd-http-query"] = req->opaque?req->opaque->Env(envlen):""; - headers["xrd-http-fullresource"] = req->resourceplusopaque.c_str(); + const char * resourcePlusOpaque = req->resourceplusopaque.c_str(); + headers["xrd-http-fullresource"] = resourcePlusOpaque != nullptr ? resourcePlusOpaque:""; headers["xrd-http-prot"] = prot->isHTTPS()?"https":"http"; // These fields usually identify the client that connected diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 9c753ae59b5..66023456957 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -409,13 +409,21 @@ int XrdHttpReq::parseFirstLine(char *line, int len) { return -1; } - // The first token cannot be too long + pos = p - line; + // The first token cannot be too long if (pos > MAX_TK_LEN - 1) { request = rtMalformed; return -2; } + // The first space-delimited char cannot be the first one + // this allows to deal with the case when a client sends a first line that starts with a space " GET / HTTP/1.1" + if(pos == 0) { + request = rtMalformed; + return -4; + } + // the first token must be non empty if (pos > 0) { line[pos] = 0; From cb7d309a2371f95fb95a59b5cb9fa8e05afb9b6e Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 11 Oct 2022 13:21:46 +0200 Subject: [PATCH 080/773] [XrdCl] AsyncRawReader: correctly trigger discard logic. --- src/XrdCl/XrdClAsyncRawReader.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClAsyncRawReader.hh b/src/XrdCl/XrdClAsyncRawReader.hh index 8bdfe7aea83..bfbe75df367 100644 --- a/src/XrdCl/XrdClAsyncRawReader.hh +++ b/src/XrdCl/XrdClAsyncRawReader.hh @@ -113,7 +113,7 @@ namespace XrdCl //-------------------------------------------------------------- // We run out of space, the server has send too much data //-------------------------------------------------------------- - if( choff == ( *chunks )[chidx].length ) + if( chidx >= chunks->size() ) { readstage = ReadDiscard; continue; From d3cb7e62ddd2153ba8be782976e4285cda504d70 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 11 Oct 2022 18:30:09 -0700 Subject: [PATCH 081/773] [Server] Properly handle opaque info for fsctl operations. --- src/XrdOfs/XrdOfs.cc | 1 + src/XrdOfs/XrdOfsFSctl.cc | 8 ++++---- src/XrdXrootd/XrdXrootdXeq.cc | 27 +++++++++++++++++++++++---- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/XrdOfs/XrdOfs.cc b/src/XrdOfs/XrdOfs.cc index 50c4cfe2e85..448f2ffc434 100644 --- a/src/XrdOfs/XrdOfs.cc +++ b/src/XrdOfs/XrdOfs.cc @@ -2657,6 +2657,7 @@ const char * XrdOfs::Split(const char *Args, const char **Opq, xlen = (*Opq)-Args; if (xlen >= Plen) xlen = Plen-1; strncpy(Path, Args, xlen); + Path[xlen] = 0; return Path; } diff --git a/src/XrdOfs/XrdOfsFSctl.cc b/src/XrdOfs/XrdOfsFSctl.cc index ff794720a51..6a1bf8e6dab 100644 --- a/src/XrdOfs/XrdOfsFSctl.cc +++ b/src/XrdOfs/XrdOfsFSctl.cc @@ -146,7 +146,7 @@ int XrdOfs::fsctl( int cmd, if (cmd & SFS_O_TRUNC) locArg = (char *)"*"; else { if (*Path == '*') {locArg = Path; Path++;} else locArg = Path; - AUTHORIZE(client,0,AOP_Stat,"locate",Path,einfo); + AUTHORIZE(client,&loc_Env,AOP_Stat,"locate",Path,einfo); } if (Finder && Finder->isRemote() && (retc = Finder->Locate(einfo, locArg, find_flag, &loc_Env))) @@ -178,7 +178,7 @@ int XrdOfs::fsctl( int cmd, const char *opq, *Path = Split(args, &opq, pbuff, sizeof(pbuff)); XrdOucEnv fs_Env(opq ? opq+1 : 0,0,client); ZTRACE(fsctl, "statfs args=" <<(args ? args : "''")); - AUTHORIZE(client,0,AOP_Stat,"statfs",Path,einfo); + AUTHORIZE(client,&fs_Env,AOP_Stat,"statfs",Path,einfo); if (Finder && Finder->isRemote() && (retc = Finder->Space(einfo, Path, &fs_Env))) return fsError(einfo, retc); @@ -196,7 +196,7 @@ int XrdOfs::fsctl( int cmd, const char *opq, *Path = Split(args, &opq, pbuff, sizeof(pbuff)); XrdOucEnv statls_Env(opq ? opq+1 : 0,0,client); ZTRACE(fsctl, "statls args=" <<(args ? args : "''")); - AUTHORIZE(client,0,AOP_Stat,"statfs",Path,einfo); + AUTHORIZE(client,&statls_Env,AOP_Stat,"statfs",Path,einfo); if (Finder && Finder->isRemote()) {statls_Env.Put("cms.qvfs", "1"); if ((retc = Finder->Space(einfo, Path, &statls_Env))) @@ -218,7 +218,7 @@ int XrdOfs::fsctl( int cmd, const char *opq, *Path = Split(args, &opq, pbuff, sizeof(pbuff)); XrdOucEnv xa_Env(opq ? opq+1 : 0,0,client); ZTRACE(fsctl, "statxa args=" <<(args ? args : "''")); - AUTHORIZE(client,0,AOP_Stat,"statxa",Path,einfo); + AUTHORIZE(client,&xa_Env,AOP_Stat,"statxa",Path,einfo); if (Finder && Finder->isRemote() && (retc = Finder->Locate(einfo,Path,SFS_O_RDONLY|SFS_O_STAT,&xa_Env))) return fsError(einfo, retc); diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index 0e570c558ab..fad603e7c62 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -915,10 +915,16 @@ int XrdXrootdProtocol::do_Locate() if (!doDig && !Squash(Path))return vpEmsg("Locating", Path); } -// Preform the actual function +// Preform the actual function. For regular Fs add back any opaque info // if (doDig) rc = digFS->fsctl(fsctl_cmd, fn, myError, CRED); - else rc = osFS->fsctl(fsctl_cmd, fn, myError, CRED); + else {if (opaque) + {int n = strlen(argp->buff); argp->buff[n] = '?'; + if ((argp->buff)+n != opaque-1) + memmove(&argp->buff[n+1], opaque, strlen(opaque)+1); + } + rc = osFS->fsctl(fsctl_cmd, fn, myError, CRED); + } TRACEP(FS, "rc=" <buff, &opaque)) return rpEmsg("Stating", argp->buff); if (!Squash(argp->buff)) return vpEmsg("Stating", argp->buff); +// Add back opaque information is present +// + if (opaque) + {int 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 // rc = osFS->fsctl(fsctl_cmd, argp->buff, myError, CRED); @@ -2862,10 +2876,15 @@ int XrdXrootdProtocol::do_Stat() if (rpCheck(argp->buff, &opaque)) return rpEmsg("Stating", argp->buff); if (!doDig && !Squash(argp->buff))return vpEmsg("Stating", argp->buff); -// Preform the actual function +// Preform the actual function, we may been to add back the opaque info // if (Request.stat.options & kXR_vfs) - {rc = osFS->fsctl(fsctl_cmd, argp->buff, myError, CRED); + {if (opaque) + {int n = strlen(argp->buff); argp->buff[n] = '?'; + if ((argp->buff)+n != opaque-1) + memmove(&argp->buff[n+1], opaque, strlen(opaque)+1); + } + rc = osFS->fsctl(fsctl_cmd, argp->buff, myError, CRED); TRACEP(FS, "rc=" <buff); if (rc == SFS_OK) Response.Send(""); } else { From 24a75a3275fba3b1e37eb3ef1ed819fe4a08f162 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 11 Oct 2022 18:32:44 -0700 Subject: [PATCH 082/773] Update notes on opaque info patch, solves authz fsctl failures. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 8f72cfb1d2a..1774d4a8fcb 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -12,6 +12,8 @@ Prerelease Notes **Commit** 360e0d47 + **Minor bug fixes** + **[Server]** Properly handle opaque info for fsctl operations. + **Commit** 0d3cb7e6 **[Apps]** Avoid SEGV when asking for help. **Commit** 016a28f7 **[Server]** Add O_RDWR open flag when creating file to avoid fs issue From 725e070f60bdc97bb1d7b5dc8e2ec3be4ade31dd Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 13 Oct 2022 11:52:52 +0200 Subject: [PATCH 083/773] [XrdHttpTPC] TPC-Pull: passing the source file size information to the OFS layer via opaque parameter "oss.asize" Implemented by doing a HEAD request on the Source server's file path and getting the content-length out of it. This solves the issue #1754 --- src/XrdTpc/XrdTpcState.cc | 34 ++++++------ src/XrdTpc/XrdTpcState.hh | 28 +++++++++- src/XrdTpc/XrdTpcTPC.cc | 110 ++++++++++++++++++++++++++------------ src/XrdTpc/XrdTpcTPC.hh | 4 +- 4 files changed, 123 insertions(+), 53 deletions(-) diff --git a/src/XrdTpc/XrdTpcState.cc b/src/XrdTpc/XrdTpcState.cc index 9dd2ae70e84..f5412752401 100644 --- a/src/XrdTpc/XrdTpcState.cc +++ b/src/XrdTpc/XrdTpcState.cc @@ -38,12 +38,14 @@ void State::Move(State &other) m_headers = other.m_headers; m_headers_copy = other.m_headers_copy; m_resp_protocol = other.m_resp_protocol; - + m_is_transfer_state = other.m_is_transfer_state; curl_easy_setopt(m_curl, CURLOPT_HEADERDATA, this); - if (m_push) { - curl_easy_setopt(m_curl, CURLOPT_READDATA, this); - } else { - curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); + if (m_is_transfer_state) { + if (m_push) { + curl_easy_setopt(m_curl, CURLOPT_READDATA, this); + } else { + curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); + } } other.m_headers_copy.clear(); other.m_curl = NULL; @@ -56,17 +58,19 @@ bool State::InstallHandlers(CURL *curl) { curl_easy_setopt(curl, CURLOPT_USERAGENT, "xrootd-tpc/" XrdVERSION); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &State::HeaderCB); curl_easy_setopt(curl, CURLOPT_HEADERDATA, this); - if (m_push) { - curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(curl, CURLOPT_READFUNCTION, &State::ReadCB); - curl_easy_setopt(curl, CURLOPT_READDATA, this); - struct stat buf; - if (SFS_OK == m_stream->Stat(&buf)) { - curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, buf.st_size); + if(m_is_transfer_state) { + if (m_push) { + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, &State::ReadCB); + curl_easy_setopt(curl, CURLOPT_READDATA, this); + struct stat buf; + if (SFS_OK == m_stream->Stat(&buf)) { + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, buf.st_size); + } + } else { + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &State::WriteCB); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); } - } else { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &State::WriteCB); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); } curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); diff --git a/src/XrdTpc/XrdTpcState.hh b/src/XrdTpc/XrdTpcState.hh index 394279b69fd..664ef46a3f6 100644 --- a/src/XrdTpc/XrdTpcState.hh +++ b/src/XrdTpc/XrdTpcState.hh @@ -31,9 +31,31 @@ public: m_content_length(-1), m_stream(NULL), m_curl(NULL), - m_headers(NULL) + m_headers(NULL), + m_is_transfer_state(true) {} + /** + * Don't use that constructor if you want to do some transfers. + * @param curl the curl handle + */ + State(CURL * curl): + m_push(true), + m_recv_status_line(false), + m_recv_all_headers(false), + m_offset(0), + m_start_offset(0), + m_status_code(-1), + m_error_code(0), + m_content_length(-1), + m_stream(NULL), + m_curl(curl), + m_headers(NULL), + m_is_transfer_state(false) + { + InstallHandlers(curl); + } + // Note that we are "borrowing" a reference to the curl handle; // it is not owned / freed by the State object. However, we use it // as if there's only one handle per State. @@ -48,7 +70,8 @@ public: m_content_length(-1), m_stream(&stream), m_curl(curl), - m_headers(NULL) + m_headers(NULL), + m_is_transfer_state(true) { InstallHandlers(curl); } @@ -143,6 +166,7 @@ private: std::vector m_headers_copy; // Copies of custom headers. std::string m_resp_protocol; // Response protocol in the HTTP status line. std::string m_error_buf; // Any error associated with a response. + bool m_is_transfer_state; // If set to true, this state will be used to perform some transfers }; }; diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index f35fda1f083..5befc42222b 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -144,33 +144,41 @@ if (purpose == CURLSOCKTYPE_IPCXN && clientp) // One special key is `authz`; this is always stripped out and copied to the Authorization // header (which will later be used for XrdSecEntity). The latter copy is only done if // the Authorization header is not already present. -static std::string prepareURL(XrdHttpExtReq &req) { - std::map::const_iterator iter = req.headers.find("xrd-http-query"); - if (iter == req.headers.end() || iter->second.empty()) {return req.resource;} - - auto has_authz_header = req.headers.find("Authorization") != req.headers.end(); - - std::istringstream requestStream(iter->second); - std::string token; - std::stringstream result; - bool found_first_header = false; - while (std::getline(requestStream, token, '&')) { - if (token.empty()) { - continue; - } else if (!strncmp(token.c_str(), "authz=", 6)) { - if (!has_authz_header) { - req.headers["Authorization"] = token.substr(6); - has_authz_header = true; - } - } else if (!found_first_header) { - result << "?" << token; - found_first_header = true; - } else { - result << "&" << token; +// +// hasSetOpaque will be set to true if at least one opaque data has been set in the URL that is returned, +// false otherwise +static std::string prepareURL(XrdHttpExtReq &req, bool & hasSetOpaque) { + std::map::const_iterator iter = req.headers.find("xrd-http-query"); + if (iter == req.headers.end() || iter->second.empty()) {return req.resource;} + + auto has_authz_header = req.headers.find("Authorization") != req.headers.end(); + + std::istringstream requestStream(iter->second); + std::string token; + std::stringstream result; + bool found_first_header = false; + while (std::getline(requestStream, token, '&')) { + if (token.empty()) { + continue; + } else if (!strncmp(token.c_str(), "authz=", 6)) { + if (!has_authz_header) { + req.headers["Authorization"] = token.substr(6); + has_authz_header = true; + } + } else if (!found_first_header) { + result << "?" << token; + found_first_header = true; + } else { + result << "&" << token; + } } - } + hasSetOpaque = found_first_header; + return req.resource + result.str().c_str(); +} - return req.resource + result.str().c_str(); +static std::string prepareURL(XrdHttpExtReq &req) { + bool foundHeader; + return prepareURL(req,foundHeader); } /******************************************************************************/ @@ -401,9 +409,11 @@ int TPCHandler::OpenWaitStall(XrdSfsFile &fh, const std::string &resource, opaque = resource.substr(pos + 1); } - // Append the authz information - opaque += (opaque.empty() ? "" : "&"); - opaque += authz; + // Append the authz information if there are some + if(!authz.empty()) { + opaque += (opaque.empty() ? "" : "&"); + opaque += authz; + } open_result = fh.open(path.c_str(), mode, openMode, &sec, opaque.c_str()); if ((open_result == SFS_STALL) || (open_result == SFS_STARTED)) { @@ -422,11 +432,14 @@ int TPCHandler::OpenWaitStall(XrdSfsFile &fh, const std::string &resource, /******************************************************************************/ #ifdef XRD_CHUNK_RESP + + + /** * Determine size at remote end. */ int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state, - bool &success, TPCLogRecord &rec) { + bool &success, TPCLogRecord &rec, bool shouldReturnErrorToClient) { success = false; curl_easy_setopt(curl, CURLOPT_NOBODY, 1); CURLcode res; @@ -436,20 +449,20 @@ int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state, ss << "Remote server failed request: " << curl_easy_strerror(res); rec.status = 500; logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss.str()); - return req.SendSimpleResp(rec.status, NULL, NULL, const_cast(curl_easy_strerror(res)), 0); + return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, const_cast(curl_easy_strerror(res)), 0) : -1; } else if (state.GetStatusCode() >= 400) { std::stringstream ss; ss << "Remote side failed with status code " << state.GetStatusCode(); rec.status = 500; logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss.str()); - return req.SendSimpleResp(rec.status, NULL, NULL, const_cast(ss.str().c_str()), 0); + return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, const_cast(ss.str().c_str()), 0): -1; } else if (res) { std::stringstream ss; ss << "HTTP library failed: " << curl_easy_strerror(res); rec.status = 500; logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss.str()); char msg[] = "Unknown internal transfer failure"; - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, msg, 0) : -1; } std::stringstream ss; ss << "Successfully determined remote size for pull request: " @@ -459,6 +472,17 @@ int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state, success = true; return 0; } + +int TPCHandler::GetContentLengthTPCPull(CURL *curl, XrdHttpExtReq &req, uint64_t &contentLength, bool & success, TPCLogRecord &rec) { + State state(curl); + int result; + //In case we cannot get the content length, we don't return anything to the client + if ((result = DetermineXferSize(curl, req, state, success, rec, false)) || !success) { + return result; + } + contentLength = state.GetContentLength(); + return result; +} /******************************************************************************/ /* XRD_CHUNK_RESP: */ @@ -936,9 +960,26 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) } } rec.streams = streams; - std::string full_url = prepareURL(req); + bool hasSetOpaque; + std::string full_url = prepareURL(req, hasSetOpaque); std::string authz = GetAuthz(req); - + curl_easy_setopt(curl, CURLOPT_URL, resource.c_str()); +#ifdef XRD_CHUNK_RESP + { + //Get the content-length of the source file and pass it to the OSS layer + //during the open + uint64_t sourceFileContentLength = 0; + bool success; + TPCLogRecord getContentLengthRec; + GetContentLengthTPCPull(curl, req, sourceFileContentLength, success, getContentLengthRec); + if(success) { + //In the case we cannot get the information from the source server (offline or other error) + //we just don't add the size information to the opaque of the local file to open + full_url += hasSetOpaque ? "&" : "?"; + full_url += "oss.asize=" + std::to_string(sourceFileContentLength); + } + } +#endif int open_result = OpenWaitStall(*fh, full_url, mode|SFS_O_WRONLY, 0644, req.GetSecEntity(), authz); if (SFS_REDIRECT == open_result) { @@ -959,7 +1000,6 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) return resp_result; } ConfigureCurlCA(curl); - curl_easy_setopt(curl, CURLOPT_URL, resource.c_str()); Stream stream(std::move(fh), streams * m_pipelining_multiplier, streams > 1 ? m_block_size : m_small_block_size, m_log); State state(0, stream, curl, false); state.CopyHeaders(req); diff --git a/src/XrdTpc/XrdTpcTPC.hh b/src/XrdTpc/XrdTpcTPC.hh index f8a63fddc9b..5b8090d5bf9 100644 --- a/src/XrdTpc/XrdTpcTPC.hh +++ b/src/XrdTpc/XrdTpcTPC.hh @@ -96,7 +96,9 @@ private: #ifdef XRD_CHUNK_RESP int DetermineXferSize(CURL *curl, XrdHttpExtReq &req, TPC::State &state, - bool &success, TPCLogRecord &); + bool &success, TPCLogRecord &, bool shouldReturnErrorToClient = true); + + int GetContentLengthTPCPull(CURL *curl, XrdHttpExtReq &req, uint64_t & contentLength, bool & success, TPCLogRecord &rec); // Send a 'performance marker' back to the TPC client, informing it of our // progress. The TPC client will use this information to determine whether From 14331c3fd43104b566212a615b9eb49ba63d0388 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 15 Oct 2022 13:32:36 -0500 Subject: [PATCH 084/773] Implement XrdSciTokensHelper interface for macaroons. In order to use ZTN with a token library, the library must provide a XrdSciTokensHelper object. This implements the interface inside the Macaroons::Authz object, allowing ZTN authentication to be utilized with a macaroon-based token. This can be enabled with the following configuration directive: ``` sec.protocol -tokenlib libXrdMacaroons-5.so ``` --- src/XrdMacaroons/XrdMacaroons.cc | 9 +- src/XrdMacaroons/XrdMacaroonsAuthz.cc | 131 ++++++++++++++++++++++++-- src/XrdMacaroons/XrdMacaroonsAuthz.hh | 20 +++- src/XrdMacaroons/export-lib-symbols | 1 + 4 files changed, 145 insertions(+), 16 deletions(-) diff --git a/src/XrdMacaroons/XrdMacaroons.cc b/src/XrdMacaroons/XrdMacaroons.cc index f2d355c55fd..6d43507f7c6 100644 --- a/src/XrdMacaroons/XrdMacaroons.cc +++ b/src/XrdMacaroons/XrdMacaroons.cc @@ -27,6 +27,7 @@ extern XrdAccAuthorize *XrdAccDefaultAuthorizeObject(XrdSysLogger *lp, const char *parm, XrdVersionInfo &myVer); +XrdSciTokensHelper *SciTokensHelper = nullptr; extern "C" { @@ -38,7 +39,9 @@ XrdAccAuthorize *XrdAccAuthorizeObjAdd(XrdSysLogger *log, { try { - return new Macaroons::Authz(log, config, chain_authz); + auto new_authz = new Macaroons::Authz(log, config, chain_authz); + SciTokensHelper = new_authz; + return new_authz; } catch (std::runtime_error &e) { @@ -109,7 +112,9 @@ XrdAccAuthorize *XrdAccAuthorizeObject(XrdSysLogger *log, } try { - return new Macaroons::Authz(log, config, chain_authz); + auto new_authz = new Macaroons::Authz(log, config, chain_authz); + SciTokensHelper = new_authz; + return new_authz; } catch (const std::runtime_error &e) { diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.cc b/src/XrdMacaroons/XrdMacaroonsAuthz.cc index 13a206f2e8a..2494e6750ff 100644 --- a/src/XrdMacaroons/XrdMacaroonsAuthz.cc +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.cc @@ -24,6 +24,7 @@ class AuthzCheck AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t m_max_duration, XrdSysError &log); const std::string &GetSecName() const {return m_sec_name;} + const std::string &GetErrorMessage() const {return m_emsg;} static int verify_before_s(void *authz_ptr, const unsigned char *pred, @@ -49,6 +50,7 @@ class AuthzCheck ssize_t m_max_duration; XrdSysError &m_log; + std::string m_emsg; const std::string m_path; std::string m_desired_activity; std::string m_sec_name; @@ -105,6 +107,24 @@ static XrdAccPrivs AddPriv(Access_Operation op, XrdAccPrivs privs) return static_cast(new_privs); } + +// Accept any value of the path, name, or activity caveats +int validate_verify_empty(void *emsg_ptr, + const unsigned char *pred, + size_t pred_sz) +{ + if ((pred_sz >= 5) && (!memcmp(reinterpret_cast(pred), "path:", 5) || + !memcmp(reinterpret_cast(pred), "name:", 5))) + { + return 0; + } + if ((pred_sz >= 9) && (!memcmp(reinterpret_cast(pred), "activity:", 9))) + { + return 0; + } + return 1; +} + } @@ -144,19 +164,29 @@ XrdAccPrivs Authz::Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *env) { - const char *authz = env ? env->Get("authz") : nullptr; // We don't allow any testing to occur in this authz module, preventing // a macaroon to be used to receive further macaroons. if (oper == AOP_Any) { return m_chain ? m_chain->Access(Entity, path, oper, env) : XrdAccPriv_None; } - if (!authz || strncmp(authz, "Bearer%20", 9)) + + const char *authz = env ? env->Get("authz") : nullptr; + if (authz && !strncmp(authz, "Bearer%20", 9)) { - //m_log.Emsg("Access", "No bearer token present"); + authz += 9; + } + + // If there's no request-specific token, check for a ZTN session token + if (!authz && Entity && !strcmp("ztn", Entity->prot) && Entity->creds && + Entity->credslen && Entity->creds[Entity->credslen] == '\0') + { + authz = Entity->creds; + } + + if (!authz) { return OnMissing(Entity, path, oper, env); } - authz += 9; macaroon_returncode mac_err = MACAROON_SUCCESS; struct macaroon* macaroon = macaroon_deserialize( @@ -238,6 +268,77 @@ Authz::Access(const XrdSecEntity *Entity, const char *path, return AddPriv(oper, XrdAccPriv_None); } +bool Authz::Validate(const char *token, + std::string &emsg, + long long *expT, + XrdSecEntity *entP) +{ + macaroon_returncode mac_err = MACAROON_SUCCESS; + std::unique_ptr macaroon( + macaroon_deserialize(token, &mac_err), + &macaroon_destroy); + + if (!macaroon) + { + emsg = "Failed to deserialize the token as a macaroon"; + // Purposely log at debug level in case if this validation is ever + // chained so we don't have overly-chatty logs. + m_log.Log(LogMask::Debug, "Validate", emsg.c_str()); + return false; + } + + std::unique_ptr verifier( + macaroon_verifier_create(), &macaroon_verifier_destroy); + if (!verifier) + { + emsg = "Internal error: failed to create a verifier."; + m_log.Log(LogMask::Error, "Validate", emsg.c_str()); + return false; + } + + // Note the path and operation here are ignored as we won't use those validators + AuthzCheck check_helper("/", AOP_Read, m_max_duration, m_log); + + if (macaroon_verifier_satisfy_general(verifier.get(), AuthzCheck::verify_before_s, &check_helper, &mac_err) || + macaroon_verifier_satisfy_general(verifier.get(), validate_verify_empty, nullptr, &mac_err)) + { + emsg = "Failed to configure the verifier"; + m_log.Log(LogMask::Error, "Validate", emsg.c_str()); + return false; + } + + const unsigned char *macaroon_loc; + size_t location_sz; + macaroon_location(macaroon.get(), &macaroon_loc, &location_sz); + if (strncmp(reinterpret_cast(macaroon_loc), m_location.c_str(), location_sz)) + { + emsg = "Macaroon contains incorrect location: " + + std::string(reinterpret_cast(macaroon_loc), location_sz); + m_log.Log(LogMask::Warning, "Validate", emsg.c_str(), ("all.sitename is " + m_location).c_str()); + return false; + } + + if (macaroon_verify(verifier.get(), macaroon.get(), + reinterpret_cast(m_secret.c_str()), + m_secret.size(), + nullptr, 0, + &mac_err)) + { + emsg = "Macaroon verification error" + (check_helper.GetErrorMessage().size() ? + (", " + check_helper.GetErrorMessage()) : ""); + m_log.Log(LogMask::Warning, "Validate", emsg.c_str()); + return false; + } + + const unsigned char *macaroon_id; + size_t id_sz; + macaroon_identifier(macaroon.get(), &macaroon_id, &id_sz); + m_log.Log(LogMask::Info, "Validate", ("Macaroon verification successful; ID " + + std::string(reinterpret_cast(macaroon_id), id_sz)).c_str()); + + return true; +} + AuthzCheck::AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log) : m_max_duration(max_duration), @@ -325,12 +426,13 @@ AuthzCheck::verify_before(const unsigned char * pred, size_t pred_sz) { return 1; } - m_log.Log(LogMask::Debug, "AuthzCheck", "running verify before", pred_str.c_str()); + m_log.Log(LogMask::Debug, "AuthzCheck", "Checking macaroon for expiration; caveat:", pred_str.c_str()); struct tm caveat_tm; if (strptime(&pred_str[7], "%Y-%m-%dT%H:%M:%SZ", &caveat_tm) == nullptr) { - m_log.Log(LogMask::Debug, "AuthzCheck", "failed to parse time string", &pred_str[7]); + m_emsg = "Failed to parse time string: " + pred_str.substr(7); + m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str()); return 1; } caveat_tm.tm_isdst = -1; @@ -338,18 +440,27 @@ AuthzCheck::verify_before(const unsigned char * pred, size_t pred_sz) time_t caveat_time = timegm(&caveat_tm); if (-1 == caveat_time) { - m_log.Log(LogMask::Debug, "AuthzCheck", "failed to generate unix time", &pred_str[7]); + m_emsg = "Failed to generate unix time: " + pred_str.substr(7); + m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str()); return 1; } if ((m_max_duration > 0) && (caveat_time > m_now + m_max_duration)) { - m_log.Log(LogMask::Warning, "AuthzCheck", "Max token age is greater than configured max duration; rejecting"); + m_emsg = "Max token age is greater than configured max duration; rejecting"; + m_log.Log(LogMask::Warning, "AuthzCheck", m_emsg.c_str()); return 1; } int result = (m_now >= caveat_time); - if (!result) m_log.Log(LogMask::Debug, "AuthzCheck", "verify before successful"); - else m_log.Log(LogMask::Debug, "AuthzCheck", "verify before failed"); + if (!result) + { + m_log.Log(LogMask::Debug, "AuthzCheck", "Macaroon has not expired."); + } + else + { + m_emsg = "Macaroon expired at " + pred_str.substr(7); + m_log.Log(LogMask::Debug, "AuthzCheck", m_emsg.c_str()); + } return result; } diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.hh b/src/XrdMacaroons/XrdMacaroonsAuthz.hh index 4a9ac3d251d..acf88bc9c73 100644 --- a/src/XrdMacaroons/XrdMacaroonsAuthz.hh +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.hh @@ -1,5 +1,6 @@ #include "XrdAcc/XrdAccAuthorize.hh" +#include "XrdSciTokens/XrdSciTokensHelper.hh" #include "XrdSys/XrdSysError.hh" @@ -8,7 +9,7 @@ class XrdSysError; namespace Macaroons { -class Authz : public XrdAccAuthorize +class Authz final : public XrdAccAuthorize, public XrdSciTokensHelper { public: Authz(XrdSysLogger *lp, const char *parms, XrdAccAuthorize *chain); @@ -18,21 +19,32 @@ public: virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, - XrdOucEnv *env); + XrdOucEnv *env) override; + + // Do a minimal validation that this is a non-expired token; used + // for session tokens. + virtual bool Validate(const char *token, + std::string &emsg, + long long *expT, + XrdSecEntity *entP) override; virtual int Audit(const int accok, const XrdSecEntity *Entity, const char *path, const Access_Operation oper, - XrdOucEnv *Env) + XrdOucEnv *Env) override { return 0; } virtual int Test(const XrdAccPrivs priv, - const Access_Operation oper) + const Access_Operation oper) override { return 0; } + // Macaroons don't have a concept off an "issuers"; return an empty + // list. + virtual Issuers IssuerList() {return Issuers();} + private: XrdAccPrivs OnMissing(const XrdSecEntity *Entity, const char *path, diff --git a/src/XrdMacaroons/export-lib-symbols b/src/XrdMacaroons/export-lib-symbols index 46713c737e4..0a6279d9046 100644 --- a/src/XrdMacaroons/export-lib-symbols +++ b/src/XrdMacaroons/export-lib-symbols @@ -3,6 +3,7 @@ global: XrdAccAuthorizeObject*; XrdAccAuthorizeObjAdd*; XrdHttpGetExtHandler*; + SciTokensHelper; local: *; From 6ef78195d2279a2e462f2aaae831859822e2a6f9 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 19 Oct 2022 17:07:00 +0200 Subject: [PATCH 085/773] [Docs] Sync release notes. --- docs/PreReleaseNotes.txt | 30 ------------------------------ docs/ReleaseNotes.txt | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 1774d4a8fcb..67abaf92dbf 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -8,37 +8,7 @@ Prerelease Notes + **New Features** + **Major bug fixes** - **[XrdFfs]** Fix a bug in xrootdfs reported by issue #1777 - **Commit** 360e0d47 + **Minor bug fixes** - **[Server]** Properly handle opaque info for fsctl operations. - **Commit** 0d3cb7e6 - **[Apps]** Avoid SEGV when asking for help. - **Commit** 016a28f7 - **[Server]** Add O_RDWR open flag when creating file to avoid fs issue - **Commit** 0a665900 - **[HTTP]** allow VO names with spaces and other quoted chars - **Commit** 639387b0 504dfb78 + **Miscellaneous** - **[TLS]** Shutdown the socket if a SSL error happens during an accept - **Commit** 9a48ecf4 ebbe8397 - **[TLS]** Trigger CRL refresh thread via the constructor. - **Commit** 19038b5a b180a248 f1acf6d4 87da7f2f f1dfad02 - **[TLS]** Free current context when a new context is generated. - **Commit** e4187ee6 411d3eb4 - **[Cmake]** add ENABLE_ switch for scitokens and macaroons, closes #1728. - **Commit** 8847a1db - **[TLS]** he CRL refresh thread logic only starts when there is a need for it. - **Commit** bbb5feea b11f8c00 - **[TLS]** The server certificate is also renewed by the Refresh thread - **Commit** 449d3e93 ff90fe2f - **[CI]** Replace cc7 builds with centos 7, part 4. - **Commit** 2f22f2d1 - **[CI]** Replace cc7 builds with centos 7, part 3. - **Commit** 4d7565a7 - **[CI]** Replace cc7 builds with centos 7, part 2. - **Commit** f3af43e9 - **[CI]** Replace cc7 builds with centos 7, part 1. - **Commit** 4bb71080 diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 129b89f7af8..08f2226ebff 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -6,6 +6,40 @@ Release Notes ============= +------------- +Version 5.5.1 +------------- + ++ **Major bug fixes** +**[XrdFfs]** Fix a bug in xrootdfs reported by issue #1777 +**[Server]** Avoid SEGV when client tries to access file after deferred closed. +**[XrdHttp]** The server certificate is renewed by the Refresh thread of the + XrdTlsContext object. +**[XrdHttp]** Fix a segv happening when a client sends a first line starting + with a space. +**[XrdTls]** Shutdown the socket if a SSL error happens when trying to accept + a connection. + ++ **Minor bug fixes** +**[Apps]** Avoid SEGV when asking for help. +**[XrdCl]** copy job: fix memory leak (buffers not queued on error). +**[Server]** Add O_RDWR open flag when creating file to avoid fs issue. +**[Server]** Properly handle opaque info for fsctl operations. +**[XrdHttp]** Allow VO names with spaces and other quoted chars. +**[XrdCl]** LocalFileHandler: fail gracefuly on overloaded machines. + ++ **Miscellaneous** +**[XrdCl]** Introduce new error code for handling local errors. +**[XrdCl]** local file handler: obtain error code with aio_error. +**[XrdCl]** xrdfs ls: sanitize ls entry. +**[CMake]** Add ENABLE_ switch for scitokens and macaroons, closes #1728. +**[XrdTls]** Start the CRLRefresh thread in XrdTlsContext constructor. +**[XrdTls]** Changed the bit set for the activation of the Refresh thread. +**[XrdTls]** The CRL refresh thread logic only starts when there is a need + for it. +**[XrdTls]** Free current context when a new context is generated. +**[XrdHttpTpc]** Pass src size to OFS via occ.asize. + ------------- Version 5.5.0 ------------- From 98265bd6b743a542e448bbfdcc61c995cb69bbe6 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Thu, 20 Oct 2022 09:02:48 +0200 Subject: [PATCH 086/773] Check all sizes (8, 16, 32, 64) in check Check operator++ in check Add ${ATOMIC_LIBRARY} to libXrdServer dependencies --- cmake/XRootDSystemCheck.cmake | 12 +++++------- src/XrdServer.cmake | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmake/XRootDSystemCheck.cmake b/cmake/XRootDSystemCheck.cmake index 52f9ba6411b..8af86886300 100644 --- a/cmake/XRootDSystemCheck.cmake +++ b/cmake/XRootDSystemCheck.cmake @@ -149,13 +149,11 @@ function(check_working_cxx_atomics varname) #include #include int main() { - std::atomic a1(0); - int a1val = a1.load(); - (void)a1val; - std::atomic a2(0); - uint64_t a2val = a2.load(std::memory_order_relaxed); - (void)a2val; - return 0; + std::atomic a1; + std::atomic a2; + std::atomic a3; + std::atomic a4; + return a1++ + a2++ + a3++ + a4++; } " ${varname}) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) diff --git a/src/XrdServer.cmake b/src/XrdServer.cmake index 4006976d529..d5d2e453347 100644 --- a/src/XrdServer.cmake +++ b/src/XrdServer.cmake @@ -191,6 +191,7 @@ target_link_libraries( XrdUtils ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} + ${ATOMIC_LIBRARY} ${EXTRA_LIBS} ${SOCKET_LIBRARY} ) From 016942782ef5fb15f906bb0fab84270f199015c8 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 20 Oct 2022 13:20:19 +0200 Subject: [PATCH 087/773] [XrdCeph] Sync sub-module. --- src/XrdCeph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCeph b/src/XrdCeph index 01399664e8d..6ba517522a7 160000 --- a/src/XrdCeph +++ b/src/XrdCeph @@ -1 +1 @@ -Subproject commit 01399664e8d2815eea00acf034d39daae7696ddf +Subproject commit 6ba517522a7a15e08ad061b96996e8ee14328014 From 46ad080f05bea2e5a79bd8852a6e9cb9cc636fa8 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 2 Nov 2022 15:24:18 +0100 Subject: [PATCH 088/773] [Server] Make sure Mkdir returns a negative code for an EEXIST error --- src/XrdOss/XrdOssApi.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdOss/XrdOssApi.cc b/src/XrdOss/XrdOssApi.cc index 5113ffbf6b3..5e453494f55 100644 --- a/src/XrdOss/XrdOssApi.cc +++ b/src/XrdOss/XrdOssApi.cc @@ -333,7 +333,7 @@ int XrdOssSys::Mkdir(const char *path, mode_t mode, int mkpath, XrdOucEnv *envP) if (!stat(local_path, &Stat) && S_ISDIR(Stat.st_mode) && mode == (Stat.st_mode & accBits)) return XrdOssOK; - return EEXIST; + return -EEXIST; } /******************************************************************************/ From ccca941197e7cef3baface31bfa0f9b6f1b07347 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 2 Nov 2022 15:28:12 +0100 Subject: [PATCH 089/773] Update notes on Mkdir return code change --- docs/PreReleaseNotes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 67abaf92dbf..e5f412c08cb 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -10,5 +10,6 @@ Prerelease Notes + **Major bug fixes** + **Minor bug fixes** + **[Server]** Make sure Mkdir returns a negative code for an EEXIST error + **Miscellaneous** From 8a6d7c09642db4465a3e0dfee47b9dfe0357fceb Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 2 Nov 2022 23:16:02 -0700 Subject: [PATCH 090/773] Allow specfication of minimum and maximum creation mode. --- src/XrdCeph | 2 +- src/XrdOfs/XrdOfs.cc | 24 +++++-- src/XrdOfs/XrdOfs.hh | 6 ++ src/XrdOfs/XrdOfsConfig.cc | 138 +++++++++++++++++++++++++++++++++++++ src/XrdOuc/XrdOucUtils.cc | 41 +++++++++++ src/XrdOuc/XrdOucUtils.hh | 2 + 6 files changed, 205 insertions(+), 8 deletions(-) diff --git a/src/XrdCeph b/src/XrdCeph index 6ba517522a7..01399664e8d 160000 --- a/src/XrdCeph +++ b/src/XrdCeph @@ -1 +1 @@ -Subproject commit 6ba517522a7a15e08ad061b96996e8ee14328014 +Subproject commit 01399664e8d2815eea00acf034d39daae7696ddf diff --git a/src/XrdOfs/XrdOfs.cc b/src/XrdOfs/XrdOfs.cc index 448f2ffc434..03c644442b1 100644 --- a/src/XrdOfs/XrdOfs.cc +++ b/src/XrdOfs/XrdOfs.cc @@ -163,9 +163,10 @@ XrdOss *XrdOfsOss; /******************************************************************************/ /* X r d O f s C o n s t r u c t o r */ -/******************************************************************************/ +/*****************************************************************************/ -XrdOfs::XrdOfs() : tpcRdrHost{}, tpcRdrPort{} +XrdOfs::XrdOfs() : dMask{0000,0775}, fMask{0000,0775}, // Legacy + tpcRdrHost{}, tpcRdrPort{} { const char *bp; @@ -498,7 +499,7 @@ int XrdOfsFile::open(const char *path, // In } } oP(path); - mode_t theMode = Mode & S_IAMB; + mode_t theMode = (Mode | XrdOfsFS->fMask[0]) & XrdOfsFS->fMask[1]; const char *tpcKey; int retc, isPosc = 0, crOpts = 0, isRW = 0, open_flag = 0; int find_flag = open_mode & (SFS_O_NOWAIT | SFS_O_RESET | SFS_O_MULTIW); @@ -506,7 +507,8 @@ int XrdOfsFile::open(const char *path, // In // Trace entry // - ZTRACE(open, Xrd::hex1 <Stat(path, &Stat, 0, &chmod_Env))) + return XrdOfsFS->Emsg(epname, einfo, retc, "change", path); + if (S_ISDIR(Stat.st_mode)) acc_mode = (acc_mode | dMask[0]) & dMask[1]; + else acc_mode = (acc_mode | fMask[0]) & fMask[1]; + // Check if we should generate an event // if (evsObject && evsObject->Enabled(XrdOfsEvs::Chmod)) @@ -2065,7 +2075,7 @@ int XrdOfs::mkdir(const char *path, // In { EPNAME("mkdir"); static const int LocOpts = SFS_O_RDWR | SFS_O_CREAT | SFS_O_META; - mode_t acc_mode = Mode & S_IAMB; + mode_t acc_mode = (Mode | dMask[0]) & dMask[1]; int retc, mkpath = Mode & SFS_O_MKPTH; const char *tident = einfo.getErrUser(); XrdOucEnv mkdir_Env(info,0,client); diff --git a/src/XrdOfs/XrdOfs.hh b/src/XrdOfs/XrdOfs.hh index a7adbd5565b..5e9fecde0e0 100644 --- a/src/XrdOfs/XrdOfs.hh +++ b/src/XrdOfs/XrdOfs.hh @@ -379,6 +379,11 @@ enum {Authorize = 0x0001, // Authorization wanted int Options; // Various options int myPort; // Port number being used +// Directory and file creation mode controls +// +mode_t dMask[2]; // Min/Max directory mode +mode_t fMask[2]; // Min/Max file mode + // TPC related things // char *tpcRdrHost[2]; // TPC redirect target or null if none @@ -510,6 +515,7 @@ int FSctl(XrdOfsFile &file, int cmd, int alen, const char *args, int Reformat(XrdOucErrInfo &); const char *theRole(int opts); int xcrds(XrdOucStream &, XrdSysError &); +int xcrm(XrdOucStream &, XrdSysError &); int xdirl(XrdOucStream &, XrdSysError &); int xexp(XrdOucStream &, XrdSysError &, bool); int xforward(XrdOucStream &, XrdSysError &); diff --git a/src/XrdOfs/XrdOfsConfig.cc b/src/XrdOfs/XrdOfsConfig.cc index b90eaa39f98..79b5bb7b149 100644 --- a/src/XrdOfs/XrdOfsConfig.cc +++ b/src/XrdOfs/XrdOfsConfig.cc @@ -216,6 +216,11 @@ int XrdOfs::Configure(XrdSysError &Eroute, XrdOucEnv *EnvInfo) { // if (ossRW == ' ') ossRW = 'w'; +// Adjust the umask to correspond to the maximum mode allowed +// + mode_t uMask = 0777 & (~(dMask[1] | fMask[1])); + umask(uMask); + // Export our role if we actually have one // if (myRole) XrdOucEnv::Export("XRDROLE", myRole); @@ -780,6 +785,7 @@ int XrdOfs::ConfigXeq(char *var, XrdOucStream &Config, TS_XPI("ckslib", theCksLib); TS_Xeq("cksrdsz", xcrds); TS_XPI("cmslib", theCmsLib); + TS_Xeq("crmode", xcrm); TS_XPI("ctllib", theCtlLib); TS_Xeq("dirlist", xdirl); TS_Xeq("forward", xforward); @@ -850,6 +856,138 @@ int XrdOfs::xcrds(XrdOucStream &Config, XrdSysError &Eroute) return 0; } +/******************************************************************************/ +/* x c r m */ +/******************************************************************************/ + +/* Function: xcrm + + Purpose: To parse the directive: crmode [dirs ] [files ] + + : common | legacy | [raw] + + common uses dirs 0700:0755 and files 0600:0644 + + legacy uses dirs 0000:0775 and files 0000:0775 + + raw Allows actual specification of mode bits without enforcing + default requirements. The resulting modes may not be 0. + Otherwise, the specified values are made consistent with + the default mode settings. + + : | : | : + + : The minimum mode value required (always set), see . + : The maximum mode value to be enforced, see . + + is either an octal mode specifiation or a standard ls type + mode specification (i.e. 'rwx'). The specification is in + groups of 3 letters. The first group designates user mode, + the scond group mode, and the last other mode. To disallow + a mode specify a dash. Note that for files, the 'x' + character must be a dash unless raw mode is enabled. It is + impossible to disllow any mode for user except for raw mode. + + Output: 0 upon success or !0 upon failure. +*/ + +int XrdOfs::xcrm(XrdOucStream &Config, XrdSysError &Eroute) +{ + static const mode_t dMin = 0700, dMax = 0775, fMin = 0600, fMax = 0664; + static const mode_t xBit = 0111, wBit = 0002; + const char *mtype; + char *colon, *val, *minM, *maxM; + mode_t mMask[2]; + bool isDirs, isRaw; + +// Get the size +// + if (!(val = Config.GetWord()) || !val[0]) + {Eroute.Emsg("Config", "crmode argument not specified"); return 1;} + +// Process all of the specs +// +do{if (!strcmp("dirs", val)) {isDirs = true; mtype = "dirs mode";} + else if (!strcmp("files", val)) {isDirs = false; mtype = "files mode";} + else {Eroute.Emsg("Config", "invalid mode type - ", val); + return 1; + } + + if (!(val = Config.GetWord()) || !val[0]) + {Eroute.Emsg("Config", mtype, "value not specified"); return 1;} + + if (!strcmp(val, "common")) + {if (isDirs) {dMask[0] = dMin; dMask[1] = dMax;} + else {fMask[0] = fMin; fMask[1] = fMax;} + continue; + } + + if (!strcmp(val, "legacy")) + {if (isDirs) {dMask[0] = 0; dMask[1] = 0775;} + else {fMask[0] = 0; fMask[1] = 0775;} + continue; + } + + if ((isRaw = !strcmp(val, "raw"))) + {if (!(val = Config.GetWord()) || !val[0]) + {Eroute.Emsg("Config", mtype, "value not specified"); return 1;} + } + + colon = index(val, ':'); + if (!colon || colon == val || *(colon+1) == 0) + {Eroute.Emsg("Config",mtype,"mode spec requires min and max values"); + return 1; + } + minM = val; *colon = 0; maxM = colon + 1; + + if (!XrdOucUtils::mode2mask(minM, mMask[0])) + {Eroute.Emsg("Config", mtype, "value is invalid -", minM); + return 1; + } + + if (!XrdOucUtils::mode2mask(maxM, mMask[1])) + {Eroute.Emsg("Config", mtype, "value is invalid -", maxM); + return 1; + } + + if (isDirs) + {if (isRaw) {dMask[0] = mMask[0]; dMask[1] = mMask[1];} + else {if ((mMask[0] | mMask[1]) & wBit) + {Eroute.Say("Config warning: 'other' w-mode removed from dirs mode!"); + mMask[0] &= ~wBit; mMask[1] &= ~wBit; + } + dMask[0] = (mMask[0] | dMin) & dMax; + dMask[1] = (mMask[1] | dMin) & dMax; + } + if ((dMask[0] & dMask[1]) != dMask[0]) + {Eroute.Emsg("Config","dirs mode min and max values are inconsistent!"); + return 1; + } + } else { // Files + if (isRaw) {fMask[0] = mMask[0]; fMask[1] = mMask[1];} + else {if ((mMask[0] | mMask[1]) & wBit) + {Eroute.Say("Config warning: 'other' w-mode removed from files mode!"); + mMask[0] &= ~wBit; mMask[1] &= ~wBit; + } + if ((mMask[0] | mMask[1]) & xBit) + {Eroute.Say("Config warning: x-mode removed from files mode!"); + mMask[0] &= ~xBit; mMask[1] &= ~xBit; + } + fMask[0] = (mMask[0] | fMin) & fMax; + fMask[1] = (mMask[1] | fMin) & fMax; + } + if ((fMask[0] & fMask[1]) != fMask[0]) + {Eroute.Emsg("Config","files mode min and max values are inconsistent!"); + return 1; + } + } + } while((val = Config.GetWord()) && val[0]); + +// All done, return success +// + return 0; +} + /******************************************************************************/ /* x d i r l */ /******************************************************************************/ diff --git a/src/XrdOuc/XrdOucUtils.cc b/src/XrdOuc/XrdOucUtils.cc index 13ca7b0fcb3..8d45818f5d4 100644 --- a/src/XrdOuc/XrdOucUtils.cc +++ b/src/XrdOuc/XrdOucUtils.cc @@ -934,6 +934,47 @@ int XrdOucUtils::makePath(char *path, mode_t mode, bool reset) return 0; } +/******************************************************************************/ +/* m o d e 2 m a s k */ +/******************************************************************************/ + +bool XrdOucUtils::mode2mask(const char *mode, mode_t &mask) +{ + mode_t mval[3] = {0}, mbit[3] = {0x04, 0x02, 0x01}; + const char *mok = "rwx"; + char mlet; + +// Accept octal mode +// + if (isdigit(*mode)) + {char *eP; + mask = strtol(mode, &eP, 8); + return *eP == 0; + } + +// Make sure we have the correct number of characters +// + int n = strlen(mode); + if (!n || n > 9 || n/3*3 != n) return false; + +// Convert groups of three +// + int k = 0; + do {for (int i = 0; i < 3; i++) + {mlet = *mode++; + if (mlet != '-') + {if (mlet != mok[i]) return false; + mval[k] |= mbit[i]; + } + } + } while(++k < 3 && *mode); + +// Combine the modes and return success +// + mask = mval[0]<<6 | mval[1]<<3 | mval[2]; + return true; +} + /******************************************************************************/ /* p a r s e L i b */ /******************************************************************************/ diff --git a/src/XrdOuc/XrdOucUtils.hh b/src/XrdOuc/XrdOucUtils.hh index 0d34de582af..9442bcc24c9 100644 --- a/src/XrdOuc/XrdOucUtils.hh +++ b/src/XrdOuc/XrdOucUtils.hh @@ -100,6 +100,8 @@ static bool makeHome(XrdSysError &eDest, const char *inst, static int makePath(char *path, mode_t mode, bool reset=false); +static bool mode2mask(const char *mode, mode_t &mask); + static bool parseLib(XrdSysError &eDest, XrdOucStream &Config, const char *libName, char *&path, char **libparm); From c0614bcf33d8c6797c480c038ed218ab19e5e98a Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 2 Nov 2022 23:20:37 -0700 Subject: [PATCH 091/773] Update notes on crmode directive. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 67abaf92dbf..4f67308220c 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,6 +6,8 @@ Prerelease Notes ================ + **New Features** +**[Server]** Allow specfication of minimum and maximum creation mode. +**Commit: 8a6d7c0 + **Major bug fixes** From 0a5c36b1b0c0dcae6288f43b91ef546a4945d61d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 20 Oct 2022 10:57:04 +0200 Subject: [PATCH 092/773] [CMake] Make switches for server-only options dependent on XRCL_ONLY=FALSE Fixes: ff67c2a0 Closes: #1804 --- cmake/XRootDDefaults.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index bbee8a275b5..5c25975bb29 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -9,6 +9,8 @@ if( "${CMAKE_BUILD_TYPE}" STREQUAL "" ) endif() endif() +include( CMakeDependentOption ) + define_default( PLUGIN_VERSION 5 ) option( ENABLE_FUSE "Enable the fuse filesystem driver if possible." TRUE ) option( ENABLE_CRYPTO "Enable the OpenSSL cryprography support." TRUE ) @@ -28,7 +30,7 @@ option( ENABLE_XRDEC "Enable erasure coding component." option( ENABLE_ASAN "Enable adress sanitizer." FALSE ) option( ENABLE_TSAN "Enable thread sanitizer." FALSE ) option( ENABLE_XRDCLHTTP "Enable xrdcl-http plugin." TRUE ) -option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE ) -option( ENABLE_MACAROONS "Enable Macaroons plugin." TRUE ) +cmake_dependent_option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE "NOT XRDCL_ONLY" FALSE ) +cmake_dependent_option( ENABLE_MACAROONS "Enable Macaroons plugin." TRUE "NOT XRDCL_ONLY" FALSE ) option( FORCE_ENABLED "Fail build if enabled components cannot be built." FALSE ) define_default( XRD_PYTHON_REQ_VERSION 3 ) From 4177404bd825cd64fcddb24de707dc9f4d442926 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 8 Nov 2022 11:58:27 +0100 Subject: [PATCH 093/773] [CMake] Drop setting of CMP0054 policy to OLD behavior This does not seem to be necessary. Eventually CMake will remove the old behavior, so it's better to switch to the new behavior. See "cmake --help-policy CMP0054" for information on this policy. --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 104b497ba0b..f338a68e126 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,11 +9,6 @@ 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) -endif() - - #------------------------------------------------------------------------------- # A 'plugins' phony target to simplify building build-tree binaries. # Plugins are responsible for adding themselves to this target, where From 180d08e1515c51ad0b684a74e89b70b3cb5e30a7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 8 Nov 2022 13:31:27 +0100 Subject: [PATCH 094/773] [CMake] Fix warnings due to package name mismatches in find modules CMake generates a warning when a find module like Find.cmake uses something other than (case-sensitive) in the call to find_package_handle_standard_args. --- cmake/{FindCPPUnit.cmake => FindCppUnit.cmake} | 2 +- cmake/FindKerberos5.cmake | 2 +- cmake/FindReadline.cmake | 2 +- cmake/{FindLibUuid.cmake => Findlibuuid.cmake} | 4 ++-- cmake/{FindSystemd.cmake => Findsystemd.cmake} | 0 cmake/XRootDFindLibs.cmake | 8 ++++---- 6 files changed, 9 insertions(+), 9 deletions(-) rename cmake/{FindCPPUnit.cmake => FindCppUnit.cmake} (90%) rename cmake/{FindLibUuid.cmake => Findlibuuid.cmake} (94%) rename cmake/{FindSystemd.cmake => Findsystemd.cmake} (100%) diff --git a/cmake/FindCPPUnit.cmake b/cmake/FindCppUnit.cmake similarity index 90% rename from cmake/FindCPPUnit.cmake rename to cmake/FindCppUnit.cmake index 9b015db1b96..cc6e7400191 100644 --- a/cmake/FindCPPUnit.cmake +++ b/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/cmake/FindKerberos5.cmake b/cmake/FindKerberos5.cmake index 345c3729d3e..eae4dd3dba7 100644 --- a/cmake/FindKerberos5.cmake +++ b/cmake/FindKerberos5.cmake @@ -32,7 +32,7 @@ else() set( KERBEROS5_LIBRARIES ${KERBEROS5_LIBRARY} ${COM_ERR_LIBRARY} ) find_package_handle_standard_args( - KERBEROS5 + Kerberos5 DEFAULT_MSG KERBEROS5_LIBRARIES KERBEROS5_INCLUDE_DIR ) diff --git a/cmake/FindReadline.cmake b/cmake/FindReadline.cmake index 99dfa442187..d79cfb36b4d 100644 --- a/cmake/FindReadline.cmake +++ b/cmake/FindReadline.cmake @@ -56,7 +56,7 @@ else( READLINE_INCLUDE_DIR AND READLINE_LIBRARY ) endif() find_package_handle_standard_args( - READLINE + Readline DEFAULT_MSG READLINE_LIBRARY READLINE_INCLUDE_DIR ) diff --git a/cmake/FindLibUuid.cmake b/cmake/Findlibuuid.cmake similarity index 94% rename from cmake/FindLibUuid.cmake rename to cmake/Findlibuuid.cmake index 94b97a9e786..f18268e5e40 100644 --- a/cmake/FindLibUuid.cmake +++ b/cmake/Findlibuuid.cmake @@ -1,5 +1,5 @@ #.rst: -# Finduuid +# Findlibuuid # ----------- # # Find libuuid, DCE compatible Universally Unique Identifier library. @@ -52,5 +52,5 @@ unset(CMAKE_REQUIRED_INCLUDES) unset(_uuid_header_only) unset(_have_libuuid) -find_package_handle_standard_args(uuid DEFAULT_MSG UUID_INCLUDE_DIR) +find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR) mark_as_advanced(UUID_INCLUDE_DIR UUID_LIBRARY) diff --git a/cmake/FindSystemd.cmake b/cmake/Findsystemd.cmake similarity index 100% rename from cmake/FindSystemd.cmake rename to cmake/Findsystemd.cmake diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 333a5d06865..9c990a819e3 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -3,7 +3,7 @@ #------------------------------------------------------------------------------- find_package( ZLIB REQUIRED) -find_package( LibUuid REQUIRED ) +find_package( libuuid REQUIRED ) if( ENABLE_READLINE ) if( FORCE_ENABLED ) @@ -30,7 +30,7 @@ if( LIBXML2_FOUND ) add_definitions( -DHAVE_XML2 ) endif() -find_package( Systemd ) +find_package( systemd ) if( SYSTEMD_FOUND ) add_definitions( -DHAVE_SYSTEMD ) endif() @@ -82,9 +82,9 @@ endif() if( ENABLE_TESTS ) if( FORCE_ENABLED ) - find_package( CPPUnit REQUIRED ) + find_package( CppUnit REQUIRED ) else() - find_package( CPPUnit ) + find_package( CppUnit ) endif() if( CPPUNIT_FOUND ) set( BUILD_TESTS TRUE ) From 2083b95d28d268e5e070e193bb21623aad64ba95 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 8 Nov 2022 13:52:41 +0100 Subject: [PATCH 095/773] [CMake] Use upstream FindOpenSSL.cmake XRootD's FindOpenSSL.cmake fails when OpenSSL is not found with the following error: CMake Error at cmake/FindOpenSSL.cmake:15 (file): file STRINGS file "/tmp/xrootd/xrootd/OPENSSL_INCLUDE_DIR-NOTFOUND/openssl/opensslv.h" cannot be read. Call Stack (most recent call first): cmake/XRootDFindLibs.cmake:44 (find_package) CMakeLists.txt:26 (include) CMake Error at cmake/FindOpenSSL.cmake:16 (string): string sub-command REGEX, mode REPLACE needs at least 6 arguments total to command. Call Stack (most recent call first): cmake/XRootDFindLibs.cmake:44 (find_package) CMakeLists.txt:26 (include) Since FindOpenSSL.cmake is already provided upstream by CMake 3.1, we can use that instead and drop our custom module. --- cmake/FindOpenSSL.cmake | 136 ------------------------------------- cmake/XRootDFindLibs.cmake | 5 +- 2 files changed, 3 insertions(+), 138 deletions(-) delete mode 100644 cmake/FindOpenSSL.cmake diff --git a/cmake/FindOpenSSL.cmake b/cmake/FindOpenSSL.cmake deleted file mode 100644 index 847e78141e9..00000000000 --- a/cmake/FindOpenSSL.cmake +++ /dev/null @@ -1,136 +0,0 @@ -include( FindPackageHandleStandardArgs ) - -set( OPENSSL_GOOD_VERSION TRUE ) -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 ) - - file(STRINGS ${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h opensslvers REGEX "^# define OPENSSL_VERSION_NUMBER") - string(REGEX REPLACE "# define[ ]+OPENSSL_VERSION_NUMBER[ ]+" "" opensslvers ${opensslvers}) - - set( LIBSSLNAME ssl ) - set( LIBCRYPTONAME crypto ) - - if ( OPENSSL_GOOD_VERSION ) - - find_library( - OPENSSL_SSL_LIBRARY - NAMES ${LIBSSLNAME} - HINTS - ${OPENSSL_ROOT_DIR} - PATH_SUFFIXES - ${LIBRARY_PATH_PREFIX} - ${LIB_SEARCH_OPTIONS}) - - string(FIND ${OPENSSL_SSL_LIBRARY} ".a" hasstaticext) - if (NOT ${hasstaticext} EQUAL -1) - if ( ${opensslvers} LESS "0x1000201fL" ) - set( OPENSSL_GOOD_VERSION FALSE ) - message(WARNING " >>> Cannot build XRootD crypto modules: static openssl build version is < 1.0.2") - else() - set(OPENSSL_USE_STATIC TRUE) - set( LIBCRYPTONAME libcrypto.a ) - endif() - message("-- Using OpenSSL static libraries (version: " ${opensslvers} ")") - endif() - - endif() - - if ( OPENSSL_GOOD_VERSION ) - find_library( - OPENSSL_CRYPTO_LIBRARY - NAMES ${LIBCRYPTONAME} - 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) - - mark_as_advanced( OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES ) - - endif() -endif() - - -#------------------------------------------------------------------------------- -# Check for the TLS support in the OpenSSL version that is available -# (assume available if use of static libs is detected and the openssl version -# is at least 1.0.2) -#------------------------------------------------------------------------------- - -if ( OPENSSL_FOUND ) - - if( OPENSSL_USE_STATIC AND OPENSSL_GOOD_VERSION ) - - add_definitions( -DHAVE_TLS -DHAVE_TLS12 -DHAVE_TLS11 -DHAVE_TLS1 -DHAVE_DH_PADDED -DHAVE_DH_PADDED_FUNC ) - - else() - - set( CMAKE_REQUIRED_LIBRARIES ${OPENSSL_LIBRARIES} ) - set( CMAKE_REQUIRED_QUIET FALSE) - - 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() - - check_function_exists(DH_compute_key_padded HAVE_DH_PADDED_FUNC) - check_symbol_exists( - DH_compute_key_padded - ${OPENSSL_INCLUDE_DIR}/openssl/dh.h - HAVE_DH_PADDED_SYMB) - if( HAVE_DH_PADDED_FUNC) - if( HAVE_DH_PADDED_SYMB ) - add_definitions( -DHAVE_DH_PADDED ) - else() - add_definitions( -DHAVE_DH_PADDED_FUNC ) - endif() - endif() - - endif() -endif() diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 9c990a819e3..d9bb661ae3d 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -39,11 +39,12 @@ find_package( CURL ) if( ENABLE_CRYPTO ) if( FORCE_ENABLED ) - find_package( OpenSSL REQUIRED ) + find_package( OpenSSL 1.0.2 REQUIRED ) else() - find_package( OpenSSL ) + find_package( OpenSSL 1.0.2 ) endif() if( OPENSSL_FOUND ) + add_definitions( -DHAVE_DH_PADDED ) add_definitions( -DHAVE_XRDCRYPTO ) add_definitions( -DHAVE_SSL ) set( BUILD_CRYPTO TRUE ) From 957a08e13f3d806687875e6a04fc60c7de8ceedc Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 8 Nov 2022 15:33:26 +0100 Subject: [PATCH 096/773] Replace usage of deprecated egrep command with grep -E The egrep command has been deprecated since 2007. Recently grep started to warn about it, so replace egrep usage with grep -E. See release notes of grep at the link below for more information. https://savannah.gnu.org/forum/forum.php?forum_id=10227 --- genversion.sh | 8 ++++---- packaging/makesrpm.sh | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/genversion.sh b/genversion.sh index 8f1bf6df0bd..f609e64020b 100755 --- a/genversion.sh +++ b/genversion.sh @@ -13,7 +13,7 @@ EXP3='^v[0-9]+\.[0-9]+\.[0-9]+\-rc.*$' function getNumericVersion() { VERSION=$1 - if test x`echo $VERSION | egrep $EXP2` == x; then + if test x`echo $VERSION | grep -E $EXP2` == x; then echo "1000000"; return; fi @@ -37,16 +37,16 @@ function getVersionFromRefs() VERSION="unknown" for i in ${REFS[@]}; do - if test x`echo $i | egrep $EXP2` != x; then + if test x`echo $i | grep -E $EXP2` != x; then echo "$i" return 0 fi - if test x`echo $i | egrep $EXP1` != x; then + if test x`echo $i | grep -E $EXP1` != x; then VERSION="$i" fi - if test x`echo $i | egrep $EXP3` != x; then + if test x`echo $i | grep -E $EXP3` != x; then VERSION="$i" fi diff --git a/packaging/makesrpm.sh b/packaging/makesrpm.sh index 143a726d61c..817ffdd2f1f 100755 --- a/packaging/makesrpm.sh +++ b/packaging/makesrpm.sh @@ -150,7 +150,7 @@ fi # Deal with release candidates #------------------------------------------------------------------------------- RELEASE=1 -if test x`echo $VERSION | egrep $RCEXP` != x; then +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 @@ -158,7 +158,7 @@ fi #------------------------------------------------------------------------------- # Deal with CERN releases #------------------------------------------------------------------------------- -if test x`echo $VERSION | egrep $CERNEXP` != x; then +if test x`echo $VERSION | grep -E $CERNEXP` != x; then RELEASE=`echo $VERSION | sed 's/.*-//'` VERSION=`echo $VERSION | sed 's/-.*\.CERN//'` fi @@ -281,7 +281,7 @@ gzip -9fn $RPMSOURCES/xrootd.tar # Check if we need some other versions #------------------------------------------------------------------------------- OTHER_VERSIONS=`cat $TEMPDIR/xrootd.spec | \ - egrep '^Source[0-9]+:[[:space:]]*xrootd-.*.gz$' |\ + grep -E '^Source[0-9]+:[[:space:]]*xrootd-.*.gz$' |\ awk '{ print $2; }'` for VER in $OTHER_VERSIONS; do From 915d91f8629927090637fe7e4027a8fb042b137f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 9 Nov 2022 16:08:09 +0100 Subject: [PATCH 097/773] [CI] Skip step to upgrade wheel for Python 2.x Attempting to upgrade it results in the error below. Complete output from command python setup.py egg_info: Traceback (most recent call last): File "", line 1, in File "/tmp/pip-build-b24Ztv/wheel/setup.py", line 1 from __future__ import annotations SyntaxError: future feature annotations is not defined --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0a4dc39e595..3bc51647e89 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -173,7 +173,7 @@ jobs: container: centos:7 steps: - # python2-pip is broken on CentOS so can't upgrade pip or setuptools + # python2-pip is broken on CentOS so can't upgrade pip, setuptools, or wheel - name: Install external dependencies with yum run: | yum update -y @@ -195,7 +195,6 @@ jobs: git \ cppunit-devel yum clean all - python2 -m pip --no-cache-dir install --upgrade wheel # Need to use v1 of action as image Git is too old - name: Clone repository now that Git is available From 2a8e04e5e55b797773ce6f2be331a4a6c7584dbb Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 9 Nov 2022 16:55:13 +0100 Subject: [PATCH 098/773] [CI] Explicitly set clang/clang++ as C/C++ compilers for macOS When not setting CMAKE_{C,CXX}_COMPILER explicitly, the default ones (cc and c++) are picked up, but are not properly setup to find headers, which causes build failures of the Python bindings. --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3bc51647e89..59a43d411c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -333,6 +333,8 @@ jobs: run: | cd .. cmake \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ -DOPENSSL_SSL_LIBRARY=$(find $(find $(brew --cellar openssl) -type d -iname "lib") -type f -iname "libssl*.dylib") \ -DOPENSSL_CRYPTO_LIBRARY=$(find $(find $(brew --cellar openssl) -type d -iname "lib") -type f -iname "libcrypto*.dylib") \ From a70e340816fc2ec6ef0127fe94a3615d2b9156e1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 9 Nov 2022 16:55:28 +0100 Subject: [PATCH 099/773] [CI] Use CMAKE_PREFIX_PATH to simplify finding openssl 3 on macOS --- .github/workflows/build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59a43d411c6..dcdac81dab1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -336,9 +336,7 @@ jobs: -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ - -DOPENSSL_SSL_LIBRARY=$(find $(find $(brew --cellar openssl) -type d -iname "lib") -type f -iname "libssl*.dylib") \ - -DOPENSSL_CRYPTO_LIBRARY=$(find $(find $(brew --cellar openssl) -type d -iname "lib") -type f -iname "libcrypto*.dylib") \ - -DOPENSSL_INCLUDE_DIR=$(find $(brew --cellar openssl) -type d -iname "include") \ + -DCMAKE_PREFIX_PATH=/usr/local/opt/openssl@3 \ -DPYTHON_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ -DPIP_OPTIONS="--verbose" \ From e3d11dc1873a5706d8092732f62bffdf7b98794c Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 9 Nov 2022 17:28:30 +0100 Subject: [PATCH 100/773] [CI] Perform a local installation of Python bindings for testing on Mac This is required as by default the installed Python doesn't know about XRootD which was installed into /usr/local/lib/python3.X. Using a local installation we can work around it. Note that we cannot set --user in PIP_OPTIONS as it conflicts with --prefix, which is added by the build system in bindings/python/CMakeLists.txt. --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dcdac81dab1..3d9501d3051 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -348,6 +348,7 @@ jobs: --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 - name: Verify install @@ -357,6 +358,7 @@ jobs: - name: Verify Python bindings run: | + export DYLD_LIBRARY_PATH=/usr/local/lib python3 --version --version python3 -m pip list python3 -m pip show xrootd From 6363b4e42078d198b02d962eb5719b0757127be4 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 10 Nov 2022 09:09:11 +0100 Subject: [PATCH 101/773] [Xrd] Fix a short writev recovery case --- src/Xrd/XrdLinkXeq.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xrd/XrdLinkXeq.cc b/src/Xrd/XrdLinkXeq.cc index 489f032ade9..ccfcd4f4fd8 100644 --- a/src/Xrd/XrdLinkXeq.cc +++ b/src/Xrd/XrdLinkXeq.cc @@ -887,7 +887,7 @@ int XrdLinkXeq::SendIOV(const struct iovec *iov, int iocnt, int bytes) {if (errno == EINTR) continue; else break; } - n -= retc; Buff += retc; + n -= retc; Buff += retc; bytesleft -= retc; } if (retc < 0 || iocnt < 1) break; } From 16848b3520c91b76985ac2b88f09180a0bbda575 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 10 Nov 2022 15:10:10 +0100 Subject: [PATCH 102/773] [XrdHttpTPC] Fixes the HTTP TPC PULL transfer issue when authentication is necessary to perform a transfer --- src/XrdTpc/XrdTpcTPC.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 5befc42222b..dd32cc65104 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -444,6 +444,9 @@ int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state, curl_easy_setopt(curl, CURLOPT_NOBODY, 1); CURLcode res; res = curl_easy_perform(curl); + //Immediately set the CURLOPT_NOBODY flag to 0 as we anyway + //don't want the next curl call to do be a HEAD request + curl_easy_setopt(curl, CURLOPT_NOBODY, 0); if (res == CURLE_HTTP_RETURNED_ERROR) { std::stringstream ss; ss << "Remote server failed request: " << curl_easy_strerror(res); @@ -468,13 +471,15 @@ int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state, ss << "Successfully determined remote size for pull request: " << state.GetContentLength(); logTransferEvent(LogMask::Debug, rec, "SIZE_SUCCESS", ss.str()); - curl_easy_setopt(curl, CURLOPT_NOBODY, 0); success = true; return 0; } int TPCHandler::GetContentLengthTPCPull(CURL *curl, XrdHttpExtReq &req, uint64_t &contentLength, bool & success, TPCLogRecord &rec) { State state(curl); + //Don't forget to copy the headers of the client's request before doing the HEAD call. Otherwise, if there is a need for authentication, + //it will fail + state.CopyHeaders(req); int result; //In case we cannot get the content length, we don't return anything to the client if ((result = DetermineXferSize(curl, req, state, success, rec, false)) || !success) { From 49d1dd9292ac32798adbcd67a437f3dff4a60af4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 11 Nov 2022 10:27:27 +0100 Subject: [PATCH 103/773] [XrdTls] Replace "#ifdef HAVE_TLS" with a version-based check This ensures that the right function is used with both shared and static libraries, as CMake's check_symbol_exists() doesn't work with static libraries. The release notes for OpenSSL 1.1.0 mention the deprecation of the old function and recommends using the new TLS_method(). More information at https://www.openssl.org/news/cl111.txt. --- src/XrdTls/XrdTlsContext.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 0a9454f9568..2dfd12b06d2 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "XrdOuc/XrdOucUtils.hh" @@ -429,7 +430,7 @@ void Fatal(std::string *eMsg, const char *msg, bool sslmsg=false) const char *GetTlsMethod(const SSL_METHOD *&meth) { -#ifdef HAVE_TLS +#if OPENSSL_VERSION_NUMBER > 0x1010000fL /* v1.1.0 */ meth = TLS_method(); #else meth = SSLv23_method(); From e4f58a1aa2f4422a9ac2a923974faeab7ccb16db Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 11 Nov 2022 14:29:23 +0100 Subject: [PATCH 104/773] [XrdEc] Fix build errors due to -Werror=pessimizing-move --- src/XrdEc/XrdEcStrmWriter.hh | 2 +- src/XrdEc/XrdEcThreadPool.hh | 2 +- src/XrdEc/XrdEcUtilities.hh | 2 +- src/XrdEc/XrdEcWrtBuff.hh | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/XrdEc/XrdEcStrmWriter.hh b/src/XrdEc/XrdEcStrmWriter.hh index f0d72a4d43d..a8f26c01f84 100644 --- a/src/XrdEc/XrdEcStrmWriter.hh +++ b/src/XrdEc/XrdEcStrmWriter.hh @@ -238,7 +238,7 @@ namespace XrdEc { std::future ftr = buffers.dequeue(); std::unique_ptr result( ftr.get() ); - return std::move( result ); + return result; } //----------------------------------------------------------------------- diff --git a/src/XrdEc/XrdEcThreadPool.hh b/src/XrdEc/XrdEcThreadPool.hh index 8ed68a981f0..0b77b0638cf 100644 --- a/src/XrdEc/XrdEcThreadPool.hh +++ b/src/XrdEc/XrdEcThreadPool.hh @@ -164,7 +164,7 @@ namespace XrdEc AnyJob *job = new AnyJob( func, std::move( args )... ); std::future ftr = job->GetFuture(); threadpool.QueueJob( job, nullptr ); - return std::move( ftr ); + return ftr; } private: diff --git a/src/XrdEc/XrdEcUtilities.hh b/src/XrdEc/XrdEcUtilities.hh index d6fa921175c..a58d3c988f8 100644 --- a/src/XrdEc/XrdEcUtilities.hh +++ b/src/XrdEc/XrdEcUtilities.hh @@ -203,7 +203,7 @@ namespace XrdEc } Element element = std::move( elements.front() ); elements.pop(); - return std::move( element ); + return element; } //------------------------------------------------------------------------- diff --git a/src/XrdEc/XrdEcWrtBuff.hh b/src/XrdEc/XrdEcWrtBuff.hh index 37b009ecd0e..f62bfe9daa7 100644 --- a/src/XrdEc/XrdEcWrtBuff.hh +++ b/src/XrdEc/XrdEcWrtBuff.hh @@ -71,7 +71,7 @@ namespace XrdEc { XrdCl::Buffer buffer( std::move( pool.front() ) ); pool.pop(); - return std::move( buffer ); + return buffer; } //--------------------------------------------------------------------- // Check if we can create a new buffer object without exceeding the @@ -81,7 +81,7 @@ namespace XrdEc { XrdCl::Buffer buffer( objcfg.blksize ); ++currentsize; - return std::move( buffer ); + return buffer; } //--------------------------------------------------------------------- // If not, we have to wait until there is a buffer we can recycle @@ -89,7 +89,7 @@ namespace XrdEc while( pool.empty() ) cv.wait( lck ); XrdCl::Buffer buffer( std::move( pool.front() ) ); pool.pop(); - return std::move( buffer ); + return buffer; } //----------------------------------------------------------------------- From b1901dbc67273a9469d81caadaee0eff748599ea Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 3 Nov 2022 15:21:57 +0100 Subject: [PATCH 105/773] [Python] Avoid crash during prepare call (#1810) --- bindings/python/src/PyXRootDFileSystem.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bindings/python/src/PyXRootDFileSystem.cc b/bindings/python/src/PyXRootDFileSystem.cc index d53fbd7fc65..ceb2c01f7a6 100644 --- a/bindings/python/src/PyXRootDFileSystem.cc +++ b/bindings/python/src/PyXRootDFileSystem.cc @@ -608,15 +608,15 @@ namespace PyXRootD { static const char *kwlist[] = { "files", "flags", "priority", "timeout", "callback", NULL }; - XrdCl::PrepareFlags::Flags flags; + uint16_t flagval = 0; 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, "OK|bHO:prepare", - (char**) kwlist, &pyfiles, &flags, &priority, &timeout, &callback ) ) + if ( !PyArg_ParseTupleAndKeywords( args, kwds, "OH|bHO:prepare", + (char**) kwlist, &pyfiles, &flagval, &priority, &timeout, &callback ) ) return NULL; if ( !PyList_Check( pyfiles ) ) { @@ -636,6 +636,9 @@ namespace PyXRootD files.push_back( std::string( file ) ); } + XrdCl::PrepareFlags::Flags flags; + flags = static_cast(flagval); + if ( callback && callback != Py_None ) { XrdCl::ResponseHandler *handler = GetHandler( callback ); if ( !handler ) return NULL; From f3ba3dc43fbd99f3a385915d999fc857ba938b8f Mon Sep 17 00:00:00 2001 From: Manuel Reis Date: Tue, 25 Oct 2022 14:23:09 +0200 Subject: [PATCH 106/773] [Python] Typo on FileSystem module methods --- bindings/python/src/PyXRootDFileSystem.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/src/PyXRootDFileSystem.hh b/bindings/python/src/PyXRootDFileSystem.hh index f884641927e..d3dd393fe3e 100644 --- a/bindings/python/src/PyXRootDFileSystem.hh +++ b/bindings/python/src/PyXRootDFileSystem.hh @@ -124,7 +124,7 @@ namespace PyXRootD { "del_xattr", (PyCFunction) PyXRootD::FileSystem::DelXAttr, METH_VARARGS | METH_KEYWORDS, NULL }, { "list_xattr", - (PyCFunction) PyXRootD::FileSystem::DelXAttr, METH_VARARGS | METH_KEYWORDS, NULL }, + (PyCFunction) PyXRootD::FileSystem::ListXAttr, METH_VARARGS | METH_KEYWORDS, NULL }, { NULL } /* Sentinel */ }; From 3d6c2e3f702d1b71d37003eafa57570c45c53f22 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 17 Nov 2022 11:55:54 +0100 Subject: [PATCH 107/773] [XrdCl] xrdcp: extended # of parallel copy jobs to 128. --- src/XrdApps/XrdCpConfig.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdApps/XrdCpConfig.cc b/src/XrdApps/XrdCpConfig.cc index f4b53e0caf0..682b2e0fe5f 100644 --- a/src/XrdApps/XrdCpConfig.cc +++ b/src/XrdApps/XrdCpConfig.cc @@ -317,7 +317,7 @@ do{while(optind < Argc && Legacy(optind)) {} if (!a2z(optarg, &xRateThreashold, 10*1024LL, -1)) Usage(22); break; case OpParallel: OpSpec |= DoParallel; - if (!a2i(optarg, &Parallel, 1, 4)) Usage(22); + if (!a2i(optarg, &Parallel, 1, 128)) Usage(22); break; case OpAllowHttp: OpSpec |= DoAllowHttp; break; From 2630fe19236beea72ff7c3504f90e6645eef5745 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 17 Nov 2022 22:55:20 -0800 Subject: [PATCH 108/773] [gsi] Add option to display DN under certain conditions. --- src/XrdSecgsi/XrdSecProtocolgsi.cc | 21 +++++++++++++++++++-- src/XrdSecgsi/XrdSecProtocolgsi.hh | 4 +++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.cc b/src/XrdSecgsi/XrdSecProtocolgsi.cc index c7bcb1d7e98..9b9b3e56159 100644 --- a/src/XrdSecgsi/XrdSecProtocolgsi.cc +++ b/src/XrdSecgsi/XrdSecProtocolgsi.cc @@ -177,6 +177,7 @@ int XrdSecProtocolgsi::VOMSCertFmt = -1; int XrdSecProtocolgsi::MonInfoOpt = 0; bool XrdSecProtocolgsi::HashCompatibility = 1; bool XrdSecProtocolgsi::TrustDNS = false; +bool XrdSecProtocolgsi::ShowDN = false; // // Crypto related info int XrdSecProtocolgsi::ncrypt = 0; // Number of factories @@ -573,6 +574,11 @@ char *XrdSecProtocolgsi::Init(gsiOptions opt, XrdOucErrInfo *erp) TrustDNS = opt.trustdns; DEBUG("trust DNS option: "< 0) { - Entity.moninfo = strdup(hs->Chain->EECname()); + if (MonInfoOpt > 0 || ShowDN) { + const char *theDN = hs->Chain->EECname(); + if (theDN) { + if (ShowDN && !GMAPuseDNname) { + PRINT(Entity.name<<" Subject DN='"< 0) Entity.moninfo = strdup(theDN); + } } if (VOMSAttrOpt > vatIgnore && VOMSFun) { @@ -2338,6 +2350,7 @@ void gsiOptions::Print(XrdOucTrace *t) POPTS(t, " MonInfo option: "<< moninfo); if (!hashcomp) POPTS(t, " Name hashing algorithm compatibility OFF"); + POPTS(t, " Show DN option: "< 0) ? true : false; if (clist.length() > 0) opts.clist = (char *)clist.c_str(); if (certdir.length() > 0) diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.hh b/src/XrdSecgsi/XrdSecProtocolgsi.hh index 88d534b798d..5c5ccfa0cd6 100644 --- a/src/XrdSecgsi/XrdSecProtocolgsi.hh +++ b/src/XrdSecgsi/XrdSecProtocolgsi.hh @@ -212,6 +212,7 @@ public: int hashcomp; // [cs] 1 send hash names with both algorithms; 0 send only the default [1] bool trustdns; // [cs] 'true' if DNS is trusted [true] + bool showDN; // [cs] 'true' display the dn gsiOptions() { debug = -1; mode = 's'; clist = 0; certdir = 0; crldir = 0; crlext = 0; cert = 0; key = 0; @@ -223,7 +224,7 @@ public: ogmap = 1; dlgpxy = 0; sigpxy = 1; srvnames = 0; exppxy = 0; authzpxy = 0; vomsat = 1; vomsfun = 0; vomsfunparms = 0; moninfo = 0; - hashcomp = 1; trustdns = true; createpxy = 1;} + hashcomp = 1; trustdns = true; showDN = false; createpxy = 1;} virtual ~gsiOptions() { } // Cleanup inside XrdSecProtocolgsiInit void Print(XrdOucTrace *t); // Print summary of gsi option status }; @@ -359,6 +360,7 @@ private: static int MonInfoOpt; static bool HashCompatibility; static bool TrustDNS; + static bool ShowDN; // // Crypto related info static int ncrypt; // Number of factories From cfc16722c31c5a57115f5ffe72e9ac0a8b4ad58c Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 17 Nov 2022 22:57:09 -0800 Subject: [PATCH 109/773] Update release notes on new gsi option. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index dcb0fa639ba..78d3cd1482a 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,6 +6,8 @@ Prerelease Notes ================ + **New Features** +**[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 From f7c423b50c201fb0b9c3754f8c7412dd52c93fb0 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 17 Nov 2022 23:06:35 -0800 Subject: [PATCH 110/773] [Server] Correct formatting when displaying the SecEntity structure. --- src/XrdSec/XrdSecEntity.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdSec/XrdSecEntity.cc b/src/XrdSec/XrdSecEntity.cc index c938027b12e..8262e545eb4 100644 --- a/src/XrdSec/XrdSecEntity.cc +++ b/src/XrdSec/XrdSecEntity.cc @@ -60,7 +60,7 @@ void XrdSecEntity::Display(XrdSysError &mDest) class AttrCB : public XrdSecEntityAttrCB {public: XrdSecEntityAttrCB::Action Attr(const char *key, const char *val) - {mDest.Say(Tid, "Attr ",key," = '", val, "'"); + {mDest.Say(Tid, " Attr ",key," = '", val, "'"); return XrdSecEntityAttrCB::Next; } AttrCB(XrdSysError &erp, const char *tid) : mDest(erp), Tid(tid) {} From b9d6ab6ab6947f2078e45681e5ae1200a4123f8a Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 17 Nov 2022 23:09:30 -0800 Subject: [PATCH 111/773] Update notes on formatting fix. --- docs/PreReleaseNotes.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 78d3cd1482a..375e3782707 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,14 +6,16 @@ Prerelease Notes ================ + **New Features** -**[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 + **[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]** Correct formatting when displaying the SecEntity structure. + **Commit: f7c423 **[Server]** Make sure Mkdir returns a negative code for an EEXIST error + **Miscellaneous** From b36bd9d7a223145b46bceaab513c08fff2308748 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Fri, 18 Nov 2022 22:28:56 -0800 Subject: [PATCH 112/773] [Apps] Cleanup xrdcp help information. --- src/XrdApps/XrdCpConfig.cc | 77 ++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/src/XrdApps/XrdCpConfig.cc b/src/XrdApps/XrdCpConfig.cc index 682b2e0fe5f..bb44ad7249b 100644 --- a/src/XrdApps/XrdCpConfig.cc +++ b/src/XrdApps/XrdCpConfig.cc @@ -85,41 +85,41 @@ const char *XrdCpConfig::opLetters = ":C:d:D:EfFhHI:NpPrRsS:t:T:vVX:y:z:ZA"; struct option XrdCpConfig::opVec[] = // For getopt_long() { + {OPT_TYPE "allow-http", 0, 0, XrdCpConfig::OpAllowHttp}, {OPT_TYPE "cksum", 1, 0, XrdCpConfig::OpCksum}, + {OPT_TYPE "coerce", 0, 0, XrdCpConfig::OpCoerce}, + {OPT_TYPE "continue", 0, 0, XrdCpConfig::OpContinue}, {OPT_TYPE "debug", 1, 0, XrdCpConfig::OpDebug}, {OPT_TYPE "dynamic-src", 0, 0, XrdCpConfig::OpDynaSrc}, - {OPT_TYPE "coerce", 0, 0, XrdCpConfig::OpCoerce}, {OPT_TYPE "force", 0, 0, XrdCpConfig::OpForce}, {OPT_TYPE "help", 0, 0, XrdCpConfig::OpHelp}, {OPT_TYPE "infiles", 1, 0, XrdCpConfig::OpIfile}, {OPT_TYPE "license", 0, 0, XrdCpConfig::OpLicense}, {OPT_TYPE "nopbar", 0, 0, XrdCpConfig::OpNoPbar}, {OPT_TYPE "notlsok", 0, 0, XrdCpConfig::OpNoTlsOK}, + {OPT_TYPE "parallel", 1, 0, XrdCpConfig::OpParallel}, {OPT_TYPE "path", 0, 0, XrdCpConfig::OpPath}, {OPT_TYPE "posc", 0, 0, XrdCpConfig::OpPosc}, {OPT_TYPE "proxy", 1, 0, XrdCpConfig::OpProxy}, {OPT_TYPE "recursive", 0, 0, XrdCpConfig::OpRecurse}, {OPT_TYPE "retry", 1, 0, XrdCpConfig::OpRetry}, + {OPT_TYPE "retry-policy", 1, 0, XrdCpConfig::OpRetryPolicy}, + {OPT_TYPE "rm-bad-cksum", 0, 0, XrdCpConfig::OpRmOnBadCksum}, {OPT_TYPE "server", 0, 0, XrdCpConfig::OpServer}, {OPT_TYPE "silent", 0, 0, XrdCpConfig::OpSilent}, {OPT_TYPE "sources", 1, 0, XrdCpConfig::OpSources}, {OPT_TYPE "streams", 1, 0, XrdCpConfig::OpStreams}, - {OPT_TYPE "tlsnodata", 0, 0, XrdCpConfig::OpTlsNoData}, {OPT_TYPE "tlsmetalink", 0, 0, XrdCpConfig::OpTlsMLF}, + {OPT_TYPE "tlsnodata", 0, 0, XrdCpConfig::OpTlsNoData}, {OPT_TYPE "tpc", 1, 0, XrdCpConfig::OpTpc}, {OPT_TYPE "verbose", 0, 0, XrdCpConfig::OpVerbose}, {OPT_TYPE "version", 0, 0, XrdCpConfig::OpVersion}, - {OPT_TYPE "xrate", 1, 0, XrdCpConfig::OpXrate}, - {OPT_TYPE "parallel", 1, 0, XrdCpConfig::OpParallel}, - {OPT_TYPE "zip", 1, 0, XrdCpConfig::OpZip}, - {OPT_TYPE "allow-http", 0, 0, XrdCpConfig::OpAllowHttp}, {OPT_TYPE "xattr", 0, 0, XrdCpConfig::OpXAttr}, - {OPT_TYPE "zip-mtln-cksum", 0, 0, XrdCpConfig::OpZipMtlnCksum}, - {OPT_TYPE "rm-bad-cksum", 0, 0, XrdCpConfig::OpRmOnBadCksum}, - {OPT_TYPE "continue", 0, 0, XrdCpConfig::OpContinue}, + {OPT_TYPE "xrate", 1, 0, XrdCpConfig::OpXrate}, {OPT_TYPE "xrate-threshold",1, 0, XrdCpConfig::OpXrateThreashold}, - {OPT_TYPE "retry-policy", 1, 0, XrdCpConfig::OpRetryPolicy}, + {OPT_TYPE "zip", 1, 0, XrdCpConfig::OpZip}, {OPT_TYPE "zip-append", 0, 0, XrdCpConfig::OpZipAppend}, + {OPT_TYPE "zip-mtln-cksum", 0, 0, XrdCpConfig::OpZipMtlnCksum}, {0, 0, 0, 0} }; @@ -929,15 +929,16 @@ void XrdCpConfig::Usage(int rc) "Usage: xrdcp [] \n"; static const char *Options= "\n" - "Options: [--cksum ] [--debug ] [--coerce] [--dynamic-src]\n" - " [--force] [--help] [--infiles ] [--license] [--nopbar]\n" - " [--notlsok] [--parallel ] [--posc] [--proxy :]\n" - " [--recursive] [--retry ] [--server] [--silent] [--sources ]\n" - " [--streams ] [--tlsnodata] [--tlsmetalink]\n" + "Options: [--allow-http] [--cksum ] [--coerce] [--continue]\n" + " [--debug ] [--dynamic-src] [--force] [--help]\n" + " [--infiles ] [--license] [--nopbar] [--notlsok]\n" + " [--parallel ] [--posc] [--proxy :]\n" + " [--recursive] [--retry ] [--retry-policy ]\n" + " [--rm-bad-cksum] [--server] [--silent] [--sources ]\n" + " [--streams ] [--tlsmetalink] [--tlsnodata]\n" " [--tpc [delegate] {first|only}] [--verbose] [--version]\n" - " [--xrate ] [--zip ] [--allow-http] [--xattr]\n" - " [--zip-mtln-cksum] [--rm-bad-cksum] [--continue]\n" - " [--xrate-threshold ] [--retry-policy ]\n"; + " [--xattr] [--xrate ] [--xrate-threshold ]\n" + " [--zip ] [--zip-append] [--zip-mtln-cksum]\n"; static const char *Syntax2= "\n" ": [[x]root[s]://[:]/] | -"; @@ -949,62 +950,66 @@ void XrdCpConfig::Usage(int rc) ": [[x]root[s]://[:]/] | -"; static const char *Detail = "\n" + "Note: using a dash (-) for uses stdin and for stdout\n\n" + "-A | --allow-http allow HTTP as source or destination protocol. Requires\n" + " the XrdClHttp client plugin\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 | zcrc32 | auto}[:{|print|source}]\n" - " If 'auto' is chosen as the checksum type, xrdcp will try to\n" - " automatically infer the right checksum type based on source/\n" - " destination configuration, source file type (metalink, ZIP), and \n" - " available checksum plug-ins.\n" + " [:{|print|source}]\n" + " where is one of adler32, crc32, crc32c, md5,\n" + " zcrc32 or auto. If 'auto' is chosen, xrdcp will try to\n" + " automatically infer the right checksum type based on the\n" + " source/destination configuration, source file type\n" + " (e.g. metalink, ZIP), and available checksum plug-ins.\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" + "-F | --coerce coerces the copy by ignoring file locking semantics\n" + " --continue continue copying a file from the point where the previous\n" + " copy was interrupted\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" + "-I | --infiles specifies the file that contains a list of input files\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" " --notlsok if server is too old to support TLS encryption fallback\n" " to unencrypted communication\n" + " --parallel number of files to copy at the same time\n" "-P | --posc enables persist on successful close semantics\n" - "-D | --proxy uses the specified SOCKS4 proxy connection\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 failed copy-jobs\n" + " --retry-policy retry policy: force or continue\n" " --rm-bad-cksum remove the target file if checksum verification failed\n" " (enables also POSC semantics)\n" - "-t | --retry maximum number of times to retry failed copy-jobs\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" + " --tlsmetalink convert [x]root to [x]roots protocol in metalinks\n" "-E | --tlsnodata in case of [x]roots protocol, encrypt only the control\n" " stream and leave the data streams unencrypted\n" - " --tlsmetalink convert [x]root to [x]roots protocol in metalinks\n" - "-T | --tpc uses third party copy mode between the src and dest.\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" + " --xattr preserve extended attributes\n" "-X | --xrate limits the transfer to the specified rate. You can\n" " suffix the value with 'k', 'm', or 'g'\n" " --xrate-threshold If the transfer rate drops below given threshold force\n" " the client to use different source or if no more sources\n" " are available fail the transfer. You can suffix the value\n" " 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" - "-A | --allow-http allow HTTP as source or destination protocol. Requires\n" - " the XrdClHttp client plugin\n" - " --xattr preserve extended attributes\n" + " --zip-append append file to existing zip archive\n" " --zip-mtln-cksum use the checksum available in a metalink file even if\n" " a file is being extracted from a ZIP archive\n" - " --continue continue copying a file from the point where the previous\n" - " copy was interrupted\n" - " --retry-policy retry policy: force or continue" + "\n" "Legacy options: [-adler] [-DI ] [-DS ] [-np]\n" " [-md5] [-OD] [-OS] [-version] [-x]"; From f00acdc7559b5189583945b4c27e06dc2637df3e Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Fri, 18 Nov 2022 22:31:19 -0800 Subject: [PATCH 113/773] Update notes on xrdcp cleanup. --- docs/PreReleaseNotes.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 375e3782707..7f49b334d1f 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -15,7 +15,9 @@ Prerelease Notes + **Minor bug fixes** **[Server]** Correct formatting when displaying the SecEntity structure. - **Commit: f7c423 + **Commit: 0f7c423 **[Server]** Make sure Mkdir returns a negative code for an EEXIST error + **Miscellaneous** + **[Apps]** Cleanup xrdcp help information. + **Commit: b36bd9d From e0d60ff1716e64952b0703414d0b93dcd5fe403b Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 15 Nov 2022 15:36:04 +0100 Subject: [PATCH 114/773] [XrdCl] Be sure to only read the header of kXR_status messages at first --- src/XrdCl/XrdClXRootDTransport.cc | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc index 109048eb855..aa0d20e22a4 100644 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ b/src/XrdCl/XrdClXRootDTransport.cc @@ -348,19 +348,24 @@ namespace XrdCl { //-------------------------------------------------------------------------- // Retrieve the body + // In case of non kXR_status responses we read all the response, including + // data. For kXR_status responses we first read only the remainder of the + // header. The header must then be unmarshalled, and then a second call of + // GetBody (repeated for suRetry as needed) will read the data. //-------------------------------------------------------------------------- size_t leftToBeRead = 0; uint32_t bodySize = 0; ServerResponseHeader* rsphdr = (ServerResponseHeader*)message.GetBuffer(); - if( rsphdr->status != kXR_status ) - bodySize = rsphdr->dlen; - else + bodySize = rsphdr->dlen; + if( rsphdr->status == kXR_status ) { - size_t stlen = sizeof( ServerResponseStatus ); // we read everything up to the offset - if( message.GetCursor() < stlen ) - bodySize = rsphdr->dlen; - else + if( message.GetCursor() >= bodySize+8 ) // we read everything up to the data[] { + const size_t stlen = sizeof( ServerResponseStatus ); + if( bodySize+8 < stlen ) + return XRootDStatus( stError, errInvalidMessage, 0, + "kXR_status: invalid message size." ); + ServerResponseStatus *rspst = (ServerResponseStatus*)message.GetBuffer(); bodySize = rspst->hdr.dlen + rspst->bdy.dlen; } From 05a459977a89583a3c47eee373361abf6a74bcc7 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 24 Nov 2022 10:33:43 +0100 Subject: [PATCH 115/773] [XrdCl] Ensure URL::GetChannelId returns an Id which can be parsed again as a url --- src/XrdCl/XrdClURL.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClURL.cc b/src/XrdCl/XrdClURL.cc index df75e20a7f3..cb2987f5451 100644 --- a/src/XrdCl/XrdClURL.cc +++ b/src/XrdCl/XrdClURL.cc @@ -493,7 +493,7 @@ namespace XrdCl //------------------------------------------------------------------------ std::string URL::GetChannelId() const { - std::string ret = pProtocol + "://" + pHostId; + std::string ret = pProtocol + "://" + pHostId + "/"; bool hascgi = false; std::string keys[] = { "xrdcl.intent", From b6d02a53098d0a1262843312201d52d9099cb403 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 24 Nov 2022 16:49:16 +0100 Subject: [PATCH 116/773] [XrdCl] test: add mock socket. --- tests/XrdClTests/SocketTest.cc | 85 ++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/tests/XrdClTests/SocketTest.cc b/tests/XrdClTests/SocketTest.cc index 0996902e466..583577daece 100644 --- a/tests/XrdClTests/SocketTest.cc +++ b/tests/XrdClTests/SocketTest.cc @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "Server.hh" #include "Utils.hh" #include "TestEnv.hh" @@ -27,6 +29,89 @@ 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 //------------------------------------------------------------------------------ From 3b87b9913e9e8f7b47b5acd0198cbdce3d0115f0 Mon Sep 17 00:00:00 2001 From: FredericHemmer Date: Thu, 24 Nov 2022 20:13:12 +0100 Subject: [PATCH 117/773] Update PyXRootDCopyProcess.cc --- bindings/python/src/PyXRootDCopyProcess.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/src/PyXRootDCopyProcess.cc b/bindings/python/src/PyXRootDCopyProcess.cc index cdcdb1b5ce8..6a322c92add 100644 --- a/bindings/python/src/PyXRootDCopyProcess.cc +++ b/bindings/python/src/PyXRootDCopyProcess.cc @@ -35,7 +35,7 @@ namespace PyXRootD { //---------------------------------------------------------------------------- - // Set the unmber of parallel jobs + // Set the number of parallel jobs //---------------------------------------------------------------------------- PyObject* CopyProcess::Parallel( CopyProcess *self, PyObject *args, PyObject *kwds ) { From 42bd828b9a4148a2f946648542eb639f9796a782 Mon Sep 17 00:00:00 2001 From: Adrian Sevcenco Date: Tue, 29 Nov 2022 14:34:48 +0200 Subject: [PATCH 118/773] fix typo s/Threashold/Threshold/ --- bindings/python/libs/client/copyprocess.py | 8 ++++---- bindings/python/src/PyXRootDCopyProcess.cc | 6 +++--- src/XrdApps/XrdCpConfig.cc | 8 ++++---- src/XrdApps/XrdCpConfig.hh | 6 +++--- src/XrdCl/XrdClClassicCopyJob.cc | 2 +- src/XrdCl/XrdClCopy.cc | 2 +- src/XrdCl/XrdClCopyProcess.cc | 4 ++-- tests/XrdClTests/FileCopyTest.cc | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bindings/python/libs/client/copyprocess.py b/bindings/python/libs/client/copyprocess.py index 2a82fc51a62..e934dc4ac90 100644 --- a/bindings/python/libs/client/copyprocess.py +++ b/bindings/python/libs/client/copyprocess.py @@ -89,7 +89,7 @@ def add_job(self, tpctimeout = 1800, rmBadCksum = False, cptimeout = 0, - xrateThreashold = 0, + xrateThreshold = 0, xrate = 0, retry = 0, cont = False, @@ -134,8 +134,8 @@ def add_job(self, :type rmBadCksum: boolean :param cptimeout: timeout for classic cp operation :type cptimeout: integer - :param xrateThreashold: data transfer rate threshold - :type xrateThreashold: integer + :param xrateThreshold: data transfer rate threshold + :type xrateThreshold: integer :param xrate: data transfer rate limit :type xrate: integer :param retry: number of retries @@ -148,7 +148,7 @@ def add_job(self, self.__process.add_job(source, target, sourcelimit, force, posc, coerce, mkdir, thirdparty, checksummode, checksumtype, checksumpreset, dynamicsource, chunksize, parallelchunks, inittimeout, - tpctimeout, rmBadCksum, cptimeout, retry, xrateThreashold, + tpctimeout, rmBadCksum, cptimeout, retry, xrateThreshold, xrate, cont, rtrplc ) def prepare(self): diff --git a/bindings/python/src/PyXRootDCopyProcess.cc b/bindings/python/src/PyXRootDCopyProcess.cc index 6a322c92add..1ba41935ad1 100644 --- a/bindings/python/src/PyXRootDCopyProcess.cc +++ b/bindings/python/src/PyXRootDCopyProcess.cc @@ -82,7 +82,7 @@ namespace PyXRootD const char *checkSumPreset = ""; bool dynamicSource = false; bool rmBadCksum = false; - long long xRateThreashold = 0; + long long xRateThreshold = 0; long long xRate = 0; long long retry = 0; bool cont = false; @@ -114,7 +114,7 @@ namespace PyXRootD &source, &target, &sourceLimit, &force, &posc, &coerce, &mkdir, &thirdParty, &checkSumMode, &checkSumType, &checkSumPreset, &dynamicSource, &chunkSize, ¶llelChunks, &initTimeout, - &tpcTimeout, &rmBadCksum, &cpTimeout, &xRateThreashold, &xRate, + &tpcTimeout, &rmBadCksum, &cpTimeout, &xRateThreshold, &xRate, &retry, &cont, &rtrplc ) ) return NULL; @@ -138,7 +138,7 @@ namespace PyXRootD properties.Set( "tpcTimeout", tpcTimeout ); properties.Set( "rmOnBadCksum", rmBadCksum ); properties.Set( "cpTimeout", cpTimeout ); - properties.Set( "xrateThreashold", xRateThreashold ); + properties.Set( "xrateThreshold", xRateThreshold ); properties.Set( "xrate", xRate ); properties.Set( "continue", cont ); diff --git a/src/XrdApps/XrdCpConfig.cc b/src/XrdApps/XrdCpConfig.cc index bb44ad7249b..3810cff0434 100644 --- a/src/XrdApps/XrdCpConfig.cc +++ b/src/XrdApps/XrdCpConfig.cc @@ -116,7 +116,7 @@ struct option XrdCpConfig::opVec[] = // For getopt_long() {OPT_TYPE "version", 0, 0, XrdCpConfig::OpVersion}, {OPT_TYPE "xattr", 0, 0, XrdCpConfig::OpXAttr}, {OPT_TYPE "xrate", 1, 0, XrdCpConfig::OpXrate}, - {OPT_TYPE "xrate-threshold",1, 0, XrdCpConfig::OpXrateThreashold}, + {OPT_TYPE "xrate-threshold",1, 0, XrdCpConfig::OpXrateThreshold}, {OPT_TYPE "zip", 1, 0, XrdCpConfig::OpZip}, {OPT_TYPE "zip-append", 0, 0, XrdCpConfig::OpZipAppend}, {OPT_TYPE "zip-mtln-cksum", 0, 0, XrdCpConfig::OpZipMtlnCksum}, @@ -141,7 +141,7 @@ XrdCpConfig::XrdCpConfig(const char *pgm) pHost = 0; pPort = 0; xRate = 0; - xRateThreashold = 0; + xRateThreshold = 0; Parallel = 1; OpSpec = 0; Dlvl = 0; @@ -313,8 +313,8 @@ do{while(optind < Argc && Legacy(optind)) {} case OpXrate: OpSpec |= DoXrate; if (!a2z(optarg, &xRate, 10*1024LL, -1)) Usage(22); break; - case OpXrateThreashold: OpSpec |= DoXrateThreashold; - if (!a2z(optarg, &xRateThreashold, 10*1024LL, -1)) Usage(22); + case OpXrateThreshold: OpSpec |= DoXrateThreshold; + if (!a2z(optarg, &xRateThreshold, 10*1024LL, -1)) Usage(22); break; case OpParallel: OpSpec |= DoParallel; if (!a2i(optarg, &Parallel, 1, 128)) Usage(22); diff --git a/src/XrdApps/XrdCpConfig.hh b/src/XrdApps/XrdCpConfig.hh index ad319736831..04170a93773 100644 --- a/src/XrdApps/XrdCpConfig.hh +++ b/src/XrdApps/XrdCpConfig.hh @@ -66,7 +66,7 @@ struct defVar const char *srcOpq; // -> -OS setting (src opaque) const char *Pgm; // -> Program name long long xRate; // -> xrate value in bytes/sec (0 if not set) - long long xRateThreashold; // -> xrate threshold value in bytes/sec (0 if not set) + long long xRateThreshold; // -> xrate threshold 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 @@ -194,8 +194,8 @@ static const uint64_t DoRmOnBadCksum = 0x0000000080000000LL; // --rm-bad-cksu static const uint64_t OpContinue = 0x10; static const uint64_t DoContinue = 0x0000000100000000LL; // --continue -static const uint64_t OpXrateThreashold = 0x11; -static const uint64_t DoXrateThreashold = 0x0000000200000000LL; // --xrate-threashold +static const uint64_t OpXrateThreshold = 0x11; +static const uint64_t DoXrateThreshold = 0x0000000200000000LL; // --xrate-threshold static const uint64_t OpRetryPolicy = 0x12; static const uint64_t DoRetryPolicy = 0x0000000400000000LL; // --retry-policy diff --git a/src/XrdCl/XrdClClassicCopyJob.cc b/src/XrdCl/XrdClClassicCopyJob.cc index fd5750f3806..6e96a0cee50 100644 --- a/src/XrdCl/XrdClClassicCopyJob.cc +++ b/src/XrdCl/XrdClClassicCopyJob.cc @@ -2456,7 +2456,7 @@ namespace XrdCl pProperties->Get( "xcpBlockSize", blockSize ); pProperties->Get( "preserveXAttr", preserveXAttr ); pProperties->Get( "xrate", xRate ); - pProperties->Get( "xrateThreashold", xRateThreshold ); + pProperties->Get( "xrateThreshold", xRateThreshold ); pProperties->Get( "rmOnBadCksum", rmOnBadCksum ); pProperties->Get( "continue", continue_ ); pProperties->Get( "cpTimeout", cpTimeout ); diff --git a/src/XrdCl/XrdClCopy.cc b/src/XrdCl/XrdClCopy.cc index 4a8360eed2f..2506da5e963 100644 --- a/src/XrdCl/XrdClCopy.cc +++ b/src/XrdCl/XrdClCopy.cc @@ -877,7 +877,7 @@ int main( int argc, char **argv ) properties.Set( "targetIsDir", targetIsDir ); properties.Set( "preserveXAttr", preserveXAttr ); properties.Set( "xrate", config.xRate ); - properties.Set( "xrateThreashold", config.xRateThreashold ); + properties.Set( "xrateThreshold", config.xRateThreshold ); properties.Set( "rmOnBadCksum", rmOnBadCksum ); properties.Set( "continue", continue_ ); properties.Set( "zipAppend", zipappend ); diff --git a/src/XrdCl/XrdClCopyProcess.cc b/src/XrdCl/XrdClCopyProcess.cc index 919f8fad199..c067062b177 100644 --- a/src/XrdCl/XrdClCopyProcess.cc +++ b/src/XrdCl/XrdClCopyProcess.cc @@ -343,11 +343,11 @@ namespace XrdCl if( !p.HasProperty( "xrate" ) ) p.Set( "xrate", 0 ); - if( !p.HasProperty( "xrateThreashold" ) || p.Get( "xrateThreashold" ) == 0 ) + if( !p.HasProperty( "xrateThreshold" ) || p.Get( "xrateThreshold" ) == 0 ) { int val = DefaultXRateThreshold; env->GetInt( "XRateThreshold", val ); - p.Set( "xrateThreashold", val ); + p.Set( "xrateThreshold", val ); } //-------------------------------------------------------------------------- diff --git a/tests/XrdClTests/FileCopyTest.cc b/tests/XrdClTests/FileCopyTest.cc index 926f0d6debd..5c9c8de4d57 100644 --- a/tests/XrdClTests/FileCopyTest.cc +++ b/tests/XrdClTests/FileCopyTest.cc @@ -426,7 +426,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) properties.Set( "source", sourceURL ); properties.Set( "target", targetURL ); properties.Set( "xrate", 1024 * 1024 * 32 ); //< limit the transfer rate to 32MB/s - properties.Set( "xrateThreashold", 1024 * 1024 * 30 ); //< fail the job if the transfer rate drops under 30MB/s + properties.Set( "xrateThreshold", 1024 * 1024 * 30 ); //< fail the job if the transfer rate drops under 30MB/s CPPUNIT_ASSERT_XRDST( process14.AddJob( properties, &results ) ); CPPUNIT_ASSERT_XRDST( process14.Prepare() ); CPPUNIT_ASSERT_XRDST( process14.Run(0) ); From 3006ff81671b20531f8a1b3830e79d760f96df40 Mon Sep 17 00:00:00 2001 From: Matthew Feickert Date: Wed, 30 Nov 2022 14:06:31 -0600 Subject: [PATCH 119/773] feat: Use setuptools over setuptools._distutils.core Drop all usage of setuptools._distutils in favor of pure setuptools. This effectively solves the problems that were present in the attempt in https://github.com/xrootd/xrootd/pull/1585 and https://github.com/xrootd/xrootd/pull/1700. Co-authored-by: Henry Schreiner --- bindings/python/setup.py.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/setup.py.in b/bindings/python/setup.py.in index fbac3e6dfb8..c5bbec50386 100644 --- a/bindings/python/setup.py.in +++ b/bindings/python/setup.py.in @@ -3,7 +3,7 @@ from __future__ import print_function # setuptools._distutils added in setuptools v48.0.0 # c.f. https://setuptools.pypa.io/en/latest/history.html#v48-0-0 try: - from setuptools._distutils.core import setup, Extension + from setuptools import setup, Extension # sysconfig v48.0.0+ is incompatible for Python 3.6 only, so fall back to distutils. # Instead of checking setuptools.__version__ explicitly, use the knowledge that # to get here in the 'try' block requires setuptools v48.0.0+. From 237708dc4bff32025f1f8550621cd5d574706ac4 Mon Sep 17 00:00:00 2001 From: Matthew Feickert Date: Wed, 30 Nov 2022 14:31:34 -0600 Subject: [PATCH 120/773] chore: Remove check for setuptools over distutils * Now that setuptools._distutils.core usage has been dropped, it is possible to go further and drop direct usage of distutils in favor of setuptools. This has already happened with the removal of setuptools._distutils as > from setuptools import setup, Extension should always pass, and so this commit just makes this explicitly clear by removing the try/except block. --- bindings/python/setup.py.in | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/bindings/python/setup.py.in b/bindings/python/setup.py.in index c5bbec50386..7fb8c53f63d 100644 --- a/bindings/python/setup.py.in +++ b/bindings/python/setup.py.in @@ -1,22 +1,14 @@ from __future__ import print_function -# setuptools._distutils added in setuptools v48.0.0 -# c.f. https://setuptools.pypa.io/en/latest/history.html#v48-0-0 -try: - from setuptools import setup, Extension - # sysconfig v48.0.0+ is incompatible for Python 3.6 only, so fall back to distutils. - # Instead of checking setuptools.__version__ explicitly, use the knowledge that - # to get here in the 'try' block requires setuptools v48.0.0+. - # 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 -except ImportError: - from distutils.core import setup, Extension +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 From 9e55d03e8b651cf6e0ccd4b9f5b4e4da47a4ba3b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 1 Dec 2022 09:09:41 +0100 Subject: [PATCH 121/773] [Python] Show install command and error out when pip install fails Issue: #1768 --- bindings/python/CMakeLists.txt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 0207a0bfa43..016638e5168 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -83,20 +83,29 @@ if ( NOT ${VALID_PIP_EXIT_CODE} EQUAL 0 ) install( CODE "EXECUTE_PROCESS( + COMMAND_ECHO STDOUT + 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( + COMMAND_ECHO STDOUT + RESULT_VARIABLE INSTALL_STATUS COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${PYTHON_EXECUTABLE} -m pip install \ ${PIP_OPTIONS} \ ${CMAKE_CURRENT_BINARY_DIR} - )" - ) + ) + if(NOT INSTALL_STATUS EQUAL 0) + message(FATAL_ERROR \"Failed to install Python bindings\") + endif() + ") endif() From be3c7ae508baaf18dcf9613bf90f3afe1834184f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 1 Dec 2022 09:36:36 +0100 Subject: [PATCH 122/773] [XrdClHttp] Link against XrdUtils for XrdOucCRC::Calc32C symbol Fixes #1815. --- src/XrdClHttp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdClHttp/CMakeLists.txt b/src/XrdClHttp/CMakeLists.txt index 5fb612d7ce2..4af924bfd0c 100644 --- a/src/XrdClHttp/CMakeLists.txt +++ b/src/XrdClHttp/CMakeLists.txt @@ -11,6 +11,6 @@ set(PLUGIN_NAME "XrdClHttp-${PLUGIN_VERSION}") add_library(${PLUGIN_NAME} MODULE ${libXrdClHttp_sources}) -target_link_libraries(${PLUGIN_NAME} ${Davix_LIBRARIES} XrdCl) +target_link_libraries(${PLUGIN_NAME} ${Davix_LIBRARIES} XrdCl XrdUtils) install(TARGETS ${PLUGIN_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) From 48d28dca54f9b90613b3568e4a7a695a783097e2 Mon Sep 17 00:00:00 2001 From: Elvin Sindrilaru Date: Thu, 1 Dec 2022 13:55:58 +0100 Subject: [PATCH 123/773] [XrdApps] Fix small memory leak --- src/XrdApps/XrdCpConfig.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdApps/XrdCpConfig.cc b/src/XrdApps/XrdCpConfig.cc index f4b53e0caf0..164fd4e81fc 100644 --- a/src/XrdApps/XrdCpConfig.cc +++ b/src/XrdApps/XrdCpConfig.cc @@ -176,6 +176,7 @@ XrdCpConfig::~XrdCpConfig() if (inFile) free(inFile); if (pHost) free(pHost); if (parmVal) free(parmVal); + if (CksObj) delete CksObj; if (CksMan) delete CksMan; if (zipFile) free(zipFile); if (dstFile) delete dstFile; From dd49e34fe7d3378382a588e79c4b2055ec20edf2 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 1 Dec 2022 10:41:58 -0800 Subject: [PATCH 124/773] Update notes on patch for missing delete.Update notes on patch for missing delete. Update notes on patch for missing delete. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 7f49b334d1f..754e7b58abe 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -14,6 +14,8 @@ Prerelease Notes + **Major bug fixes** + **Minor bug fixes** + **[XrdApps]** Fix small memory leak when checksum fails + **Commit: a8ad65a **[Server]** Correct formatting when displaying the SecEntity structure. **Commit: 0f7c423 **[Server]** Make sure Mkdir returns a negative code for an EEXIST error From bcf82f98a93d94aca20702b5895154e9c4b6f4b2 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 2 Dec 2022 13:43:10 +0100 Subject: [PATCH 125/773] [CI] Fix typo in build step names --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d9501d3051..baf12dd012f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -608,7 +608,7 @@ jobs: python3 -m pip --verbose install --upgrade ./dist/xrootd-*.tar.gz python3 -m pip list - - name: Show site-pacakges layout for XRootD modules + - 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 \ @@ -664,7 +664,7 @@ jobs: python3 -m pip --verbose install --upgrade ./dist/xrootd-*.tar.gz python3 -m pip list - - name: Show site-pacakges layout for XRootD modules + - 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 \ @@ -718,7 +718,7 @@ jobs: python3 -m pip --verbose install --upgrade ./dist/xrootd-*.tar.gz python3 -m pip list - - name: Show site-pacakges layout for XRootD modules + - 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 \ From 6ac260c64f9ac9a35e1eadd61c9eea61d5e6d910 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 2 Dec 2022 12:45:03 +0100 Subject: [PATCH 126/773] [CI] Install into /usr on Ubuntu build instead of /usr/local This allows to not rely on exporting extra variables to check the installation. On Ubuntu, dist-packages was necessary instead of site-packages for PYTHONPATH, which caused CI failures. --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index baf12dd012f..3cf8a6e41b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -269,7 +269,7 @@ jobs: run: | cd .. cmake \ - -DCMAKE_INSTALL_PREFIX=/usr/local/ \ + -DCMAKE_INSTALL_PREFIX=/usr \ -DPYTHON_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ -DPIP_OPTIONS="--verbose" \ @@ -290,9 +290,6 @@ jobs: - name: Verify Python bindings run: | - export LD_LIBRARY_PATH="/usr/local/lib:${LD_LIBRARY_PATH}" - export PYTHON_VERSION_MINOR=$(python3 -c 'import sys; print(f"{sys.version_info.minor}")') - export PYTHONPATH="/usr/local/lib/python3.${PYTHON_VERSION_MINOR}/site-packages:${PYTHONPATH}" python3 --version --version python3 -m pip list python3 -m pip show xrootd From c5edd9f36b5ea05b813741d971f711228514092e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 2 Dec 2022 16:09:09 +0100 Subject: [PATCH 127/773] [CI] Fix Ubuntu build on GitHub Actions As done in commit 0e07032346a627ef410d65b8464b5b5a21ced5bd. --- .github/workflows/build.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3cf8a6e41b5..69e434ab44f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -525,18 +525,13 @@ jobs: - 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 ${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 + 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: | From 7410d53917c0c822533f352570ad2aad1b9533be Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sun, 4 Dec 2022 21:56:09 +0100 Subject: [PATCH 128/773] [XrdCeph] Re-sync accidentally reverted sub-module. --- src/XrdCeph | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCeph b/src/XrdCeph index 01399664e8d..6ba517522a7 160000 --- a/src/XrdCeph +++ b/src/XrdCeph @@ -1 +1 @@ -Subproject commit 01399664e8d2815eea00acf034d39daae7696ddf +Subproject commit 6ba517522a7a15e08ad061b96996e8ee14328014 From 1f80cbb0d1a5efc55b19215b72972e668616d6de Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Dec 2022 16:34:50 +0100 Subject: [PATCH 129/773] [Python] Do not use COMMAND_ECHO, not available before CMake 3.15 Fixes: 117d279dc --- bindings/python/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 016638e5168..739aaae6c67 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -83,7 +83,6 @@ if ( NOT ${VALID_PIP_EXIT_CODE} EQUAL 0 ) install( CODE "EXECUTE_PROCESS( - COMMAND_ECHO STDOUT RESULT_VARIABLE INSTALL_STATUS COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${PYTHON_EXECUTABLE} ${SETUP_PY} install \ --verbose \ @@ -98,7 +97,6 @@ else() install( CODE "EXECUTE_PROCESS( - COMMAND_ECHO STDOUT RESULT_VARIABLE INSTALL_STATUS COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${PYTHON_EXECUTABLE} -m pip install \ ${PIP_OPTIONS} \ From 850a14fba57b27a73e98bdb891238be84bc1bbc5 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 12 Dec 2022 22:25:27 -0800 Subject: [PATCH 130/773] [PSS] Allow origin to be a directory of a locally mounted file system. --- src/XrdPss/XrdPss.cc | 5 +++-- src/XrdPss/XrdPss.hh | 1 + src/XrdPss/XrdPssConfig.cc | 31 +++++++++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/XrdPss/XrdPss.cc b/src/XrdPss/XrdPss.cc index ec30e565a01..e07b08f4d2e 100644 --- a/src/XrdPss/XrdPss.cc +++ b/src/XrdPss/XrdPss.cc @@ -1354,12 +1354,13 @@ int XrdPssSys::P2URL(char *pbuff, int pblen, XrdPssUrlInfo &uInfo, bool doN2N) // Format the header into the buffer and check if we overflowed. Note that we // defer substitution of the path as we need to know where the path is. // - pfxLen = snprintf(pbuff, pblen, hdrData, uInfo.getID(), path); + if (fileOrgn) pfxLen = snprintf(pbuff, pblen, hdrData, path); + else pfxLen = snprintf(pbuff, pblen, hdrData, uInfo.getID(), path); if (pfxLen >= pblen) return -ENAMETOOLONG; // Add any cgi information // - if (uInfo.hasCGI()) + if (!fileOrgn && uInfo.hasCGI()) {if (!uInfo.addCGI(pbuff, pbuff+pfxLen, pblen-pfxLen)) return -ENAMETOOLONG; } diff --git a/src/XrdPss/XrdPss.hh b/src/XrdPss/XrdPss.hh index e864a0983cf..798312a727e 100644 --- a/src/XrdPss/XrdPss.hh +++ b/src/XrdPss/XrdPss.hh @@ -190,6 +190,7 @@ XrdOucPListAnchor XPList; // Exported path list static XrdNetSecurity *Police[PolNum]; static XrdOucTList *ManList; +static char *fileOrgn; static const char *protName; static const char *hdrData; static int hdrLen; diff --git a/src/XrdPss/XrdPssConfig.cc b/src/XrdPss/XrdPssConfig.cc index be335f028f9..5c6fc12338e 100644 --- a/src/XrdPss/XrdPssConfig.cc +++ b/src/XrdPss/XrdPssConfig.cc @@ -101,6 +101,7 @@ XrdOucPListAnchor XrdPssSys::XPList; XrdNetSecurity *XrdPssSys::Police[XrdPssSys::PolNum] = {0, 0}; XrdOucTList *XrdPssSys::ManList = 0; + char *XrdPssSys::fileOrgn = 0; const char *XrdPssSys::protName = "root:"; const char *XrdPssSys::hdrData = ""; int XrdPssSys::hdrLen = 0; @@ -210,7 +211,7 @@ int XrdPssSys::Configure(const char *cfn, XrdOucEnv *envP) // Make sure we have some kind of origin // - if (!ManList && !outProxy) + if (!ManList && !outProxy && !fileOrgn) {eDest.Emsg("Config", "Origin for proxy service not specified."); return 1; } @@ -320,7 +321,8 @@ int XrdPssSys::Configure(const char *cfn, XrdOucEnv *envP) // const char *outeq = (outProxy ? "= " : ""); if (ManList) sprintf(theRdr, "%s%s:%d", outeq, ManList->text, ManList->val); - else strcpy(theRdr, outeq); + else if (fileOrgn) sprintf(theRdr, "%s%s", outeq, fileOrgn); + else strcpy(theRdr, outeq); XrdOucEnv::Export("XRDXROOTD_PROXY", theRdr); XrdOucEnv::Export("XRDXROOTD_ORIGIN", theRdr); // Backward compatibility @@ -330,6 +332,15 @@ int XrdPssSys::Configure(const char *cfn, XrdOucEnv *envP) {hdrLen = sprintf(theRdr, "%s%%s%s:%d/%%s", protName, ManList->text, ManList->val); hdrData = strdup(theRdr); + } else { + if (fileOrgn) +//?? {if (!(myFeatures & XRDOSS_HASCACH)) +// {eDest.Emsg("Config", "File origins only supported for caching proxies."); +// return 1; +// } + {hdrLen = sprintf(theRdr, "%s%s%%s", protName, fileOrgn); + hdrData = strdup(theRdr); + } } // Check if we have any r/w exports as this will determine whether or not we @@ -726,6 +737,22 @@ int XrdPssSys::xorig(XrdSysError *errp, XrdOucStream &Config) } else outProxy = false; +// We must always cleanup the file origin if it exists +// + if (fileOrgn) {free(fileOrgn); fileOrgn = 0;} + +// Check if dest is some local filesystem +// + if (*val == '/') + {char *vP = val +strlen(val) - 1; + while(*vP == '/'&& vP != val) {*vP-- = 0;} + if (ManList) {delete ManList; ManList = 0;} + protName = "file://"; + fileOrgn = strdup(val); + return 0; + } + + // Check if the is a url, if so, the protocol, must be supported // if ((colon = index(val, ':')) && *(colon+1) == '/' && *(colon+2) == '/') From f0db1821f2ced34755671bd745ce7e042976248b Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 12 Dec 2022 22:27:37 -0800 Subject: [PATCH 131/773] Update notes on new Pss/Pfc feature. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 754e7b58abe..1f6207ba78b 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,6 +6,8 @@ Prerelease Notes ================ + **New Features** + **[PSS]** Allow origin to be a directory of a locally mounted file system. + **Commit: 850a14f **[Server]** Add gsi option to display DN when it differs from entity name. **Commit: 2630fe1 **[Server]** Allow specfication of minimum and maximum creation mode. From f07732e70006e8d719f1d36cdd2cc689d35a5f27 Mon Sep 17 00:00:00 2001 From: Derek Weitzel Date: Tue, 13 Dec 2022 09:43:46 -0600 Subject: [PATCH 132/773] Adding the OverrideINIReader for SciTokens The default INI reader will concat duplicate section+name settings with a '\n' separator. The new OverrideINIReader will simply take the last value in the INI configuration file. --- src/XrdSciTokens/XrdSciTokensAccess.cc | 48 +++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 93a72fe2460..3c1b883c44b 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -286,6 +286,52 @@ struct IssuerConfig } +class OverrideINIReader: public INIReader { +public: + OverrideINIReader(XrdSysError log) + : m_log(log) {}; + OverrideINIReader(std::string filename, XrdSysError log) + : INIReader(filename), + m_log(log) { + + } + OverrideINIReader(FILE *file, XrdSysError log) + : INIReader(file), + m_log(log) { + + } +protected: + /** + * Override the ValueHandler function in order override previous values + * For example: + * [Issuer https://chtc.cs.wisc.edu/icecube] + * issuer = https://chtc.cs.wisc.edu/icecube + * base_path = /icecube/path1 + + * [Issuer https://chtc.cs.wisc.edu/icecube] + * issuer = https://chtc.cs.wisc.edu/icecube + * base_path = /icecube/path2 + * + * Will result in a configuration with base_path set to /icecube/path2 + */ + inline int ValueHandler(void* user, const char* section, const char* name, + const char* value) { + OverrideINIReader* reader = (OverrideINIReader*)user; + std::string key = MakeKey(section, name); + if (reader->_values[key].size() > 0) { + std::ostringstream os; + os << "Duplicate section and value, overriding previous value: section=" << section << ", name=" << name; + m_log.Log(LogMask::Debug, "INIConfig", os.str().c_str()); + } + + reader->_values[key] = value; + reader->_sections.insert(section); + return 1; + } + + XrdSysError m_log; + +}; class XrdAccRules { @@ -1009,7 +1055,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper } m_log.Log(LogMask::Info, "Reconfig", "Parsing configuration file:", m_cfg_file.c_str()); - INIReader reader(m_cfg_file); + OverrideINIReader reader(m_cfg_file, m_log); if (reader.ParseError() < 0) { std::stringstream ss; ss << "Error opening config file (" << m_cfg_file << "): " << strerror(errno); From 05e6a268cbb0b3268cb2e4ed0803f16046877083 Mon Sep 17 00:00:00 2001 From: Derek Weitzel Date: Tue, 13 Dec 2022 13:31:07 -0600 Subject: [PATCH 133/773] Adding duplicate section docs to scitokens readme --- src/XrdSciTokens/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/XrdSciTokens/README.md b/src/XrdSciTokens/README.md index baca00d2c95..87d4948ac51 100644 --- a/src/XrdSciTokens/README.md +++ b/src/XrdSciTokens/README.md @@ -55,6 +55,20 @@ default_user = osg name_mapfile = /path/to/mapfile ``` +Duplicate section and settings names (not values) will take the value of the last entry in the file. For example: + +``` +[Issuer https://chtc.cs.wisc.edu/icecube] +issuer = https://chtc.cs.wisc.edu/icecube +base_path = /icecube/path1 + +[Issuer https://chtc.cs.wisc.edu/icecube] +issuer = https://chtc.cs.wisc.edu/icecube +base_path = /icecube/path2 +``` + +Will result in a configuration with `issuer = https://chtc.cs.wisc.edu/icecube` and `base_path = /icecube/path2`. + Within the `Global` section, the available attributes are: - `audience` (optional): A comma separated list of acceptable audiences. The tokens must have an `aud` attribute From 1b5c346e0ac27b6319118f5751d3a6a4a13969e5 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 13 Dec 2022 13:10:39 -0800 Subject: [PATCH 134/773] Update notes on SciTokens config parsing fix. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 1f6207ba78b..b62c63c8583 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -23,5 +23,7 @@ Prerelease Notes **[Server]** Make sure Mkdir returns a negative code for an EEXIST error + **Miscellaneous** + **[SciTokens]** Add the OverrideINIReader for SciTokens to fix value parsing + **Commit: cdae187 **[Apps]** Cleanup xrdcp help information. **Commit: b36bd9d From 7bc289e014949af4353576a6a726d99429b967fd Mon Sep 17 00:00:00 2001 From: Derek Weitzel Date: Tue, 13 Dec 2022 15:17:59 -0600 Subject: [PATCH 135/773] Correct the calling of SciTokens Config Parser --- src/XrdSciTokens/XrdSciTokensAccess.cc | 27 ++++++++------------------ 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 3c1b883c44b..22699bd67d3 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -288,17 +288,12 @@ struct IssuerConfig class OverrideINIReader: public INIReader { public: - OverrideINIReader(XrdSysError log) - : m_log(log) {}; - OverrideINIReader(std::string filename, XrdSysError log) - : INIReader(filename), - m_log(log) { - + OverrideINIReader() {}; + inline OverrideINIReader(std::string filename) { + _error = ini_parse(filename.c_str(), ValueHandler, this); } - OverrideINIReader(FILE *file, XrdSysError log) - : INIReader(file), - m_log(log) { - + inline OverrideINIReader(FILE *file) { + _error = ini_parse_file(file, ValueHandler, this); } protected: /** @@ -314,23 +309,17 @@ class OverrideINIReader: public INIReader { * * Will result in a configuration with base_path set to /icecube/path2 */ - inline int ValueHandler(void* user, const char* section, const char* name, + inline static int ValueHandler(void* user, const char* section, const char* name, const char* value) { OverrideINIReader* reader = (OverrideINIReader*)user; std::string key = MakeKey(section, name); - if (reader->_values[key].size() > 0) { - std::ostringstream os; - os << "Duplicate section and value, overriding previous value: section=" << section << ", name=" << name; - m_log.Log(LogMask::Debug, "INIConfig", os.str().c_str()); - } + // Overwrite existing values, if they exist reader->_values[key] = value; reader->_sections.insert(section); return 1; } - XrdSysError m_log; - }; class XrdAccRules @@ -1055,7 +1044,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper } m_log.Log(LogMask::Info, "Reconfig", "Parsing configuration file:", m_cfg_file.c_str()); - OverrideINIReader reader(m_cfg_file, m_log); + OverrideINIReader reader(m_cfg_file); if (reader.ParseError() < 0) { std::stringstream ss; ss << "Error opening config file (" << m_cfg_file << "): " << strerror(errno); From 2b128b6814251a742588caf14cbdbf24bf111892 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 13 Dec 2022 23:14:06 -0800 Subject: [PATCH 136/773] Correct commit hashes. --- docs/PreReleaseNotes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index b62c63c8583..84f7febcae7 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -24,6 +24,6 @@ Prerelease Notes + **Miscellaneous** **[SciTokens]** Add the OverrideINIReader for SciTokens to fix value parsing - **Commit: cdae187 + **Commit: cdae187a 5c696e3d **[Apps]** Cleanup xrdcp help information. **Commit: b36bd9d From d7f8b844ca706d65b7c1cbba53b1e82e6aa3a889 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 14 Dec 2022 10:52:23 +0100 Subject: [PATCH 137/773] [XrdCl] xrdcp: make sure cp-target symlink dst is stripped from cgi. --- src/XrdCl/XrdClClassicCopyJob.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/XrdCl/XrdClClassicCopyJob.cc b/src/XrdCl/XrdClClassicCopyJob.cc index 6e96a0cee50..236884bba1c 100644 --- a/src/XrdCl/XrdClClassicCopyJob.cc +++ b/src/XrdCl/XrdClClassicCopyJob.cc @@ -1798,9 +1798,13 @@ namespace { std::string targeturl; pFile->GetProperty( "LastURL", targeturl ); + targeturl = URL( targeturl ).GetLocation(); if( symlink( targeturl.c_str(), cptarget.c_str() ) == -1 ) log->Warning( UtilityMsg, "Could not create cp-target symlink: %s", XrdSysE2T( errno ) ); + else + log->Info( UtilityMsg, "Created cp-target symlink: %s -> %s", + cptarget.c_str(), targeturl.c_str() ); } StatInfo *info = 0; From 0f79a38c06c4b81ae8e3d40b8621ffa92cc88393 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 14 Dec 2022 14:02:14 +0100 Subject: [PATCH 138/773] [XrdHttpTPC] PULL: Initialize the boolean for the opaque separator to be set properly This issue was discovered when a HTTP TPC PULL from a non-xrootd server was happening. The oss.asize attribute was put after a '&' instead of a '?' --- 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 dd32cc65104..4f4cbbe20eb 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -965,7 +965,7 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) } } rec.streams = streams; - bool hasSetOpaque; + bool hasSetOpaque = false; std::string full_url = prepareURL(req, hasSetOpaque); std::string authz = GetAuthz(req); curl_easy_setopt(curl, CURLOPT_URL, resource.c_str()); From a5ef86b94d2ff4f07664d0f72e18a6f854628ab9 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 14 Dec 2022 22:28:05 -0800 Subject: [PATCH 139/773] [Pss] Convert leftover print statement to debug action. --- src/XrdPss/XrdPss.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/XrdPss/XrdPss.cc b/src/XrdPss/XrdPss.cc index e07b08f4d2e..dd62da51649 100644 --- a/src/XrdPss/XrdPss.cc +++ b/src/XrdPss/XrdPss.cc @@ -1111,6 +1111,8 @@ ssize_t XrdPssFile::Write(const void *buff, off_t offset, size_t blen) int XrdPssFile::Fstat(struct stat *buff) { + EPNAME("fstat"); + // If we have a file descriptor then return a stat for it // if (fd >= 0) return (XrdPosixXrootd::Fstat(fd, buff) ? -errno : XrdOssOK); @@ -1148,8 +1150,7 @@ int XrdPssFile::Fstat(struct stat *buff) if (rpInfo->dstURL) free(rpInfo->dstURL); rpInfo->dstURL = strdup(lnkbuff); rpInfo->fSize = 1; -std::cerr<<"Pss_fstat: "<tprPath<<" maps " - < "<tprPath<<" maps "< "< Date: Wed, 14 Dec 2022 22:32:15 -0800 Subject: [PATCH 140/773] Update noteson Pss print statement fix. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 84f7febcae7..e666c40c1f4 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -23,6 +23,8 @@ Prerelease Notes **[Server]** Make sure Mkdir returns a negative code for an EEXIST error + **Miscellaneous** + **[Pss]** Convert leftover print statement to debug action. + **Commit: 0aef86b **[SciTokens]** Add the OverrideINIReader for SciTokens to fix value parsing **Commit: cdae187a 5c696e3d **[Apps]** Cleanup xrdcp help information. From e88666b4c40e0b78439ca1ba2e98cefcf0b56c2e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 14 Dec 2022 16:37:42 +0100 Subject: [PATCH 141/773] [XrdOuc] Adjust data pointer in XrdOucPgrwUtils::csVer Closes #1864. --- src/XrdOuc/XrdOucPgrwUtils.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdOuc/XrdOucPgrwUtils.cc b/src/XrdOuc/XrdOucPgrwUtils.cc index 8ca4d2ade5c..a1bdcee0d28 100644 --- a/src/XrdOuc/XrdOucPgrwUtils.cc +++ b/src/XrdOuc/XrdOucPgrwUtils.cc @@ -203,9 +203,11 @@ bool XrdOucPgrwUtils::csVer(dataInfo &dInfo, off_t &bado, int &badc) if (pgNum >= 0) {bado = dInfo.offs + (pgPageSize * pgNum); int xlen = (bado - dInfo.offs); + dInfo.data += xlen; dInfo.offs += xlen; dInfo.count -= xlen; badc = (dInfo.count <= pgPageSize ? dInfo.count : pgPageSize); + dInfo.data += badc; dInfo.offs += badc; dInfo.count -= badc; dInfo.csval += (pgNum+1); From 45764213e9c841489904748c23bbf9c3f60c7c3c Mon Sep 17 00:00:00 2001 From: ccaffy <85744538+ccaffy@users.noreply.github.com> Date: Thu, 15 Dec 2022 11:16:53 +0100 Subject: [PATCH 142/773] Update PreReleaseNotes.txt after commit 0f79a38 --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index e666c40c1f4..7ff9b84be31 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -21,6 +21,8 @@ Prerelease Notes **[Server]** Correct formatting when displaying the SecEntity structure. **Commit: 0f7c423 **[Server]** Make sure Mkdir returns a negative code for an EEXIST error + **[XrdTpcTPC]** Fix wrong delimiter of file's opaque data when "oss.asize" is appended + **Commit: 0f79a38 + **Miscellaneous** **[Pss]** Convert leftover print statement to debug action. From 65dd72f1249119546bd1506e56758225a4cbcc7c Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 15 Dec 2022 16:11:36 +0100 Subject: [PATCH 143/773] Update PreReleaseNotes.txt for checksum fix --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 7ff9b84be31..95bff93ea7b 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -14,6 +14,8 @@ Prerelease Notes **Commit: 8a6d7c0 + **Major bug fixes** + **[XrdOuc]** Fix checksum verification in XrdOucPgrwUtils::csVer + **Commit: e88666b + **Minor bug fixes** **[XrdApps]** Fix small memory leak when checksum fails From bb550ea2691fc6103e2febbb738907634cd7f059 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 15 Dec 2022 23:07:50 -0800 Subject: [PATCH 144/773] [PSS] Make sure local mount point origins are read/only. --- src/XrdPss/XrdPss.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/XrdPss/XrdPss.cc b/src/XrdPss/XrdPss.cc index dd62da51649..500a310a6ed 100644 --- a/src/XrdPss/XrdPss.cc +++ b/src/XrdPss/XrdPss.cc @@ -759,9 +759,12 @@ int XrdPssFile::Open(const char *path, int Oflag, mode_t Mode, XrdOucEnv &Env) // If we are opening this in r/w mode make sure we actually can // - if (rwMode && (popts & XRDEXP_NOTRW)) - {if (popts & XRDEXP_FORCERO && !tpcMode) Oflag = O_RDONLY; - else return -EROFS; + if (rwMode) + {if (XrdPssSys::fileOrgn) return -EROFS; + if (popts & XRDEXP_NOTRW) + {if (popts & XRDEXP_FORCERO && !tpcMode) Oflag = O_RDONLY; + else return -EROFS; + } } // If this is a third party copy open, then strange rules apply. If this is an From afd830789a992a64e3fa97583b8359b8ce8eb432 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 15 Dec 2022 23:10:07 -0800 Subject: [PATCH 145/773] Update notes on read/only local moint point origins. --- docs/PreReleaseNotes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 95bff93ea7b..73c6a7acef6 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -7,7 +7,7 @@ Prerelease Notes + **New Features** **[PSS]** Allow origin to be a directory of a locally mounted file system. - **Commit: 850a14f + **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. From e66958b37d81060e90795df4fd34e94961ccc718 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Fri, 16 Dec 2022 11:02:18 +0100 Subject: [PATCH 146/773] XrdMacaroons: Fix authentication when diffrent tokens are used in the same TCP session --- src/XrdMacaroons/XrdMacaroonsAuthz.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.cc b/src/XrdMacaroons/XrdMacaroonsAuthz.cc index 2494e6750ff..6c47d476931 100644 --- a/src/XrdMacaroons/XrdMacaroonsAuthz.cc +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.cc @@ -261,7 +261,7 @@ Authz::Access(const XrdSecEntity *Entity, const char *path, if (Entity && check_helper.GetSecName().size()) { const std::string &username = check_helper.GetSecName(); m_log.Log(LogMask::Debug, "Access", "Setting the request name to", username.c_str()); - Entity->eaAPI->Add("request.name", username); + Entity->eaAPI->Add("request.name", username,true); } // We passed verification - give the correct privilege. From 93c9d246967e5e70a84d26dbda4c432dce7b5402 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Fri, 16 Dec 2022 11:05:33 +0100 Subject: [PATCH 147/773] Updated PreReleaseNotes.txt for fix 1811 issue --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 73c6a7acef6..f1dbbbf8c2d 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -25,6 +25,8 @@ Prerelease Notes **[Server]** Make sure Mkdir returns a negative code for an EEXIST error **[XrdTpcTPC]** Fix wrong delimiter of file's opaque data when "oss.asize" is appended **Commit: 0f79a38 + **[XrdMacaroons] Fix authentication when different tokens are used in the same TCP session** + **Commit: 4410d56 + **Miscellaneous** **[Pss]** Convert leftover print statement to debug action. From 6adb9319368235daf31e5b72ddfc2b7265abc1c5 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 21 Dec 2022 11:00:24 +0100 Subject: [PATCH 148/773] XrdTlsContext: Recreate session cache in the Clone() method This fixes the issue #1874 --- src/XrdTls/XrdTlsContext.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 2dfd12b06d2..d31ae3d6709 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -72,6 +72,8 @@ struct XrdTlsContextImpl bool crlRunning; bool flsRunning; time_t lastCertModTime = 0; + int sessionCacheOpts = -1; + std::string sessionCacheId; }; /******************************************************************************/ @@ -779,7 +781,13 @@ XrdTlsContext *XrdTlsContext::Clone(bool full,bool startCRLRefresh) // Verify that the context was built // - if (xtc->isOK()) return xtc; + if (xtc->isOK()) { + if(pImpl->sessionCacheOpts != -1){ + //A SessionCache() call was done for the current context, so apply it for this new cloned context + xtc->SessionCache(pImpl->sessionCacheOpts,pImpl->sessionCacheId.c_str(),pImpl->sessionCacheId.size()); + } + return xtc; + } // We failed, cleanup. // @@ -940,6 +948,9 @@ int XrdTlsContext::SessionCache(int opts, const char *id, int idlen) long sslopt = 0; int flushT = opts & scFMax; + pImpl->sessionCacheOpts = opts; + pImpl->sessionCacheId = id; + // If initialization failed there is nothing to do // if (pImpl->ctx == 0) return 0; From 56fb0b02bc66e22f3e146dd153f347cae77c13e1 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 10 Jan 2023 09:56:20 -0800 Subject: [PATCH 149/773] Update prerelease notes on TLS session cache fix. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index f1dbbbf8c2d..ed99bc1aeda 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -14,6 +14,8 @@ Prerelease Notes **Commit: 8a6d7c0 + **Major bug fixes** + **[TLS]** XrdTlsContext: Recreate session cache in the Clone() method. + **Commit: 93c9d24 **[XrdOuc]** Fix checksum verification in XrdOucPgrwUtils::csVer **Commit: e88666b From a0ce75a75b47c7d62cb81da00174edbb467a0faa Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 17 Jan 2023 12:39:48 +0100 Subject: [PATCH 150/773] [XrdCl] Fix regression in ZIP CD parsing. --- src/XrdCl/XrdClZipArchive.cc | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/XrdCl/XrdClZipArchive.cc b/src/XrdCl/XrdClZipArchive.cc index fd82f6a0c19..cba1e464d2f 100644 --- a/src/XrdCl/XrdClZipArchive.cc +++ b/src/XrdCl/XrdClZipArchive.cc @@ -438,14 +438,15 @@ namespace XrdCl else std::tie( cdvec, cdmap ) = CDFH::Parse( buff, eocd->cdSize, eocd->nbCdRec ); log->Dump( ZipMsg, "[0x%x] CD records parsed.", this ); - uint64_t sumCompSize = 0; - for (auto it = cdvec.begin(); it != cdvec.end(); it++) { - sumCompSize += (*it)->compressedSize; - if ((*it)->offset > archsize || (*it)->offset + (*it)->compressedSize > archsize) - throw bad_data(); - } - if (sumCompSize > archsize) - throw bad_data(); + uint64_t sumCompSize = 0; + for (auto it = cdvec.begin(); it != cdvec.end(); it++) + { + sumCompSize += (*it)->IsZIP64() ? (*it)->extra->compressedSize : (*it)->compressedSize; + if ((*it)->offset > archsize || (*it)->offset + (*it)->compressedSize > archsize) + throw bad_data(); + } + if (sumCompSize > archsize) + throw bad_data(); } catch( const bad_data &ex ) { From 1029e7a44ee982d6c973021d48fe66b2fb732c47 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Tue, 17 Jan 2023 01:18:11 +0100 Subject: [PATCH 151/773] Add missing include causing build failure with gcc 13 /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiPages.hh:95:22: error: field 'fn_' has incomplete type 'const std::string' {aka 'const std::__cxx11::basic_string'} 95 | const std::string fn_; | ^~~ In file included from /usr/include/c++/13/iosfwd:41, from /usr/include/c++/13/bits/shared_ptr.h:52, from /usr/include/c++/13/condition_variable:45, from /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiRanges.hh:38, from /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiRanges.cc:32: /usr/include/c++/13/bits/stringfwd.h:72:11: note: declaration of 'std::string' {aka 'class std::__cxx11::basic_string'} 72 | class basic_string; | ^~~~~~~~~~~~ /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiPages.hh:96:22: error: field 'tident_' has incomplete type 'const std::string' {aka 'const std::__cxx11::basic_string'} 96 | const std::string tident_; | ^~~~~~~ /usr/include/c++/13/bits/stringfwd.h:72:11: note: declaration of 'std::string' {aka 'class std::__cxx11::basic_string'} 72 | class basic_string; | ^~~~~~~~~~~~ /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiPages.hh:148:4: error: return type 'std::string' {aka 'class std::__cxx11::basic_string'} is incomplete 148 | { | ^ /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiPages.hh:161:4: error: return type 'std::string' {aka 'class std::__cxx11::basic_string'} is incomplete 161 | { | ^ /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiPages.hh:174:4: error: return type 'std::string' {aka 'class std::__cxx11::basic_string'} is incomplete 174 | { | ^ /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiPages.hh:186:4: error: return type 'std::string' {aka 'class std::__cxx11::basic_string'} is incomplete 186 | { | ^ /builddir/build/BUILD/xrootd-5.5.1/src/XrdOssCsi/XrdOssCsiPages.hh:195:4: error: return type 'std::string' {aka 'class std::__cxx11::basic_string'} is incomplete 195 | { | ^ --- src/XrdOssCsi/XrdOssCsiPages.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdOssCsi/XrdOssCsiPages.hh b/src/XrdOssCsi/XrdOssCsiPages.hh index 852a3add6f9..cd1c1b4a392 100644 --- a/src/XrdOssCsi/XrdOssCsiPages.hh +++ b/src/XrdOssCsi/XrdOssCsiPages.hh @@ -38,6 +38,7 @@ #include "XrdOssCsiRanges.hh" #include #include +#include #include #include #include From 32dd095e97ad298fffd58488c876849da24ea031 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Tue, 17 Jan 2023 10:40:42 +0100 Subject: [PATCH 152/773] Fix build failure due to possible large memory allocation /builddir/build/BUILD/xrootd-5.5.1/src/XrdPosix/XrdPosixAdmin.cc: In member function 'FanOut': /builddir/build/BUILD/xrootd-5.5.1/src/XrdPosix/XrdPosixAdmin.cc:69:27: error: argument 1 value '4294967295' exceeds maximum object size 2147483647 [-Werror=alloc-size-larger-than=] 69 | uVec = new XrdCl::URL[i]; | ^ /usr/include/c++/13/new:128:26: note: in a call to allocation function 'operator new []' declared here 128 | _GLIBCXX_NODISCARD void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc) | ^ --- src/XrdPosix/XrdPosixAdmin.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/XrdPosix/XrdPosixAdmin.cc b/src/XrdPosix/XrdPosixAdmin.cc index 2cad5517ee5..eb8ccdecd9e 100644 --- a/src/XrdPosix/XrdPosixAdmin.cc +++ b/src/XrdPosix/XrdPosixAdmin.cc @@ -29,6 +29,7 @@ /******************************************************************************/ #include +#include #include #include #include @@ -66,6 +67,8 @@ XrdCl::URL *XrdPosixAdmin::FanOut(int &num) // Allocate an array large enough to hold this information // if (!(i = info->GetSize())) {delete info; return 0;} + if (i > std::numeric_limits::max() / sizeof(XrdCl::URL)) + {delete info; return 0;} uVec = new XrdCl::URL[i]; // Now start filling out the array From 3dcb0d861992207b300afd7f53a55d298ebee068 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Thu, 12 Jan 2023 12:50:30 -0600 Subject: [PATCH 153/773] Add a default value for "role" which should handle VOMS attributes where role is missing entirely --- src/XrdVoms/XrdVomsMapfile.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdVoms/XrdVomsMapfile.cc b/src/XrdVoms/XrdVomsMapfile.cc index 824edd94a49..8ffbe921875 100644 --- a/src/XrdVoms/XrdVomsMapfile.cc +++ b/src/XrdVoms/XrdVomsMapfile.cc @@ -269,11 +269,11 @@ XrdVomsMapfile::Apply(XrdSecEntity &entity) int from_vorg = 0, from_role = 0, from_grps = 0; XrdOucString vorg = entity.vorg, entry_vorg; - XrdOucString role = entity.role, entry_role; + XrdOucString role = entity.role ? entity.role : "", entry_role = "NULL"; XrdOucString grps = entity.grps, entry_grps; if (m_edest) m_edest->Log(LogMask::Debug, "VOMSMapfile", "Applying VOMS mapfile to incoming credential"); while (((from_vorg = vorg.tokenize(entry_vorg, from_vorg, ' ')) != -1) && - ((from_role = role.tokenize(entry_role, from_role, ' ')) != -1) && + ((role == "") || (from_role = role.tokenize(entry_role, from_role, ' ')) != -1) && ((from_grps = grps.tokenize(entry_grps, from_grps, ' ')) != -1)) { auto fqan = MakePath(entry_grps); From cf4c57b51ef4d7ba3756583d8c3af628326f3ffd Mon Sep 17 00:00:00 2001 From: Fabio Andrijauskas Date: Thu, 15 Dec 2022 18:19:57 -0800 Subject: [PATCH 154/773] Update README --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 51f87415143..f45c2ff9e06 100644 --- a/README +++ b/README @@ -23,7 +23,7 @@ XRootD requires at minimum following packages (RHEL distro): - * gcc-c++, cmake(3), krb5-devel, libuuid-devel, libxml2-devel, openssl-devel, systemd-devel, zlib-devel + * gcc-c++ cmake(3) krb5-devel libuuid-devel libxml2-devel openssl-devel systemd-devel zlib-devel * devtoolset-7 (only RHEL7) 2.1 Build system From 30d6dbc5b932bb080578237ea32dbfaab5915aab Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 19 Jan 2023 16:50:46 +0100 Subject: [PATCH 155/773] XrdTpcTPC: HTTP TPC PULL - file size request fail because of the non configuration of the server CA before calling the HEAD request --- src/XrdTpc/XrdTpcTPC.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 4f4cbbe20eb..6d327806bcd 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -969,14 +969,14 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) std::string full_url = prepareURL(req, hasSetOpaque); std::string authz = GetAuthz(req); curl_easy_setopt(curl, CURLOPT_URL, resource.c_str()); + ConfigureCurlCA(curl); #ifdef XRD_CHUNK_RESP { //Get the content-length of the source file and pass it to the OSS layer //during the open uint64_t sourceFileContentLength = 0; bool success; - TPCLogRecord getContentLengthRec; - GetContentLengthTPCPull(curl, req, sourceFileContentLength, success, getContentLengthRec); + GetContentLengthTPCPull(curl, req, sourceFileContentLength, success, rec); if(success) { //In the case we cannot get the information from the source server (offline or other error) //we just don't add the size information to the opaque of the local file to open @@ -1004,7 +1004,6 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) fh->close(); return resp_result; } - ConfigureCurlCA(curl); Stream stream(std::move(fh), streams * m_pipelining_multiplier, streams > 1 ? m_block_size : m_small_block_size, m_log); State state(0, stream, curl, false); state.CopyHeaders(req); From e63fe755524362f3dcd4c4e9b1244309901ee8f9 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 23 Jan 2023 07:22:22 -0800 Subject: [PATCH 156/773] [Server] Part 1: Follow RFC for multiple chksums in http (issue 1707). --- src/XrdXrootd/XrdXrootdConfig.cc | 6 ++++++ src/XrdXrootd/XrdXrootdXeq.cc | 12 ++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/XrdXrootd/XrdXrootdConfig.cc b/src/XrdXrootd/XrdXrootdConfig.cc index 08746b24164..bbe470b9667 100644 --- a/src/XrdXrootd/XrdXrootdConfig.cc +++ b/src/XrdXrootd/XrdXrootdConfig.cc @@ -316,14 +316,20 @@ int XrdXrootdProtocol::Configure(char *parms, XrdProtocol_Config *pi) // if (JobCKT && JobLCL) {XrdOucErrInfo myError("Config"); + XrdOucString csList(1024); XrdOucTList *tP = JobCKTLST; + int csNum = 0; 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(); + if (csNum) csList += ','; + csList.append(csNum); csList.append(':'); csList.append(tP->text); + csNum++; tP = tP->next; } while(tP); + if (csNum) XrdOucEnv::Export("XRD_CSLIST", csList.c_str()); } // Initialiaze for AIO. If we are not in debug mode and aio is enabled then we diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index fad603e7c62..c4e0619ab3a 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -1979,18 +1979,14 @@ int XrdXrootdProtocol::do_Qconf() bp += n; bleft -= n; } else if (!strcmp("chksum", val)) - {if (!JobCKT) + {const char *csList = getenv("XRD_CSLIST"); + if (!JobCKT || !csList) {n = snprintf(bp, bleft, "chksum\n"); bp += n; bleft -= n; continue; } - XrdOucTList *tP = JobCKTLST; - char sep; - do {sep = (tP->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); + n = snprintf(bp, bleft, "%s\n", csList); + bp += n; bleft -= n; } else if (!strcmp("cid", val)) {const char *cidval = getenv("XRDCMSCLUSTERID"); From c421d3d35041772c86576ee94200360f128f375d Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 23 Jan 2023 07:30:11 -0800 Subject: [PATCH 157/773] Update notes on part 1 fix. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index ed99bc1aeda..6a55e1a63cb 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -20,6 +20,8 @@ Prerelease Notes **Commit: e88666b + **Minor bug fixes** + **[Server]** Part 1: Follow RFC for multiple chksums in http (issue 1707). + **Commit: e63fe75 **[XrdApps]** Fix small memory leak when checksum fails **Commit: a8ad65a **[Server]** Correct formatting when displaying the SecEntity structure. From 7c44e645abd3865e01695977bc9c22ae08179970 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 16 Jan 2023 16:11:55 +0100 Subject: [PATCH 158/773] XrdHttpReq: Corrected needs_base64_padding logic --- src/XrdHttp/XrdHttpReq.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 66023456957..5641cb79df4 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -148,13 +148,13 @@ static bool needs_base64_padding(const std::string &rfc_name) return true; } else if (!strcasecmp(rfc_name.c_str(), "adler32")) { return false; - } else if (strcasecmp(rfc_name.c_str(), "SHA")) { + } else if (!strcasecmp(rfc_name.c_str(), "SHA")) { return true; - } else if (strcasecmp(rfc_name.c_str(), "SHA-256")) { + } else if (!strcasecmp(rfc_name.c_str(), "SHA-256")) { return true; - } else if (strcasecmp(rfc_name.c_str(), "SHA-512")) { + } else if (!strcasecmp(rfc_name.c_str(), "SHA-512")) { return true; - } else if (strcasecmp(rfc_name.c_str(), "UNIXcksum")) { + } else if (!strcasecmp(rfc_name.c_str(), "UNIXcksum")) { return false; } return false; From 8c9fbb3409e936b2f36861bcc0085c02dc0a9272 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 30 Jan 2023 17:21:57 +0100 Subject: [PATCH 159/773] XrdHttp: Modified the checksum handling logic a) Ignore unsupported checksums b) On the first supported checksum return that checksum c) If no requested checksums are supported return the default checksum (with the proper name) d) If checksums are not supported at all, return an error. (405 Method not allowed) Fixes #1707 --- src/XrdCl/XrdClUtils.hh | 21 ++----------- src/XrdHttp/XrdHttpReq.cc | 63 +++++++++++++++++++++++++++++++++++++-- src/XrdHttp/XrdHttpReq.hh | 8 +++++ src/XrdUtils/XrdUtils.hh | 60 +++++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 src/XrdUtils/XrdUtils.hh diff --git a/src/XrdCl/XrdClUtils.hh b/src/XrdCl/XrdClUtils.hh index 552ac03be0f..3b66115945a 100644 --- a/src/XrdCl/XrdClUtils.hh +++ b/src/XrdCl/XrdClUtils.hh @@ -31,6 +31,7 @@ #include "XrdCl/XrdClPostMaster.hh" #include "XrdCl/XrdClXRootDTransport.hh" #include "XrdNet/XrdNetUtils.hh" +#include "XrdUtils/XrdUtils.hh" #include @@ -56,25 +57,7 @@ namespace XrdCl 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 ); + XrdUtils::Utils::splitString(result,input,delimiter); } //------------------------------------------------------------------------ diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 5641cb79df4..00058e29d0f 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -49,7 +49,7 @@ #include "Xrd/XrdLink.hh" #include "XrdXrootd/XrdXrootdBridge.hh" #include "Xrd/XrdBuffer.hh" - +#include "XrdUtils/XrdUtils.hh" #include #include #include @@ -91,6 +91,10 @@ static std::string convert_digest_rfc_name(const std::string &rfc_name_multiple) return "SHA-512"; } else if (!strcasecmp(rfc_name.c_str(), "UNIXcksum")) { return "UNIXcksum"; + } else if (!strcasecmp(rfc_name.c_str(), "crc32")) { + return "crc32"; + } else if (!strcasecmp(rfc_name.c_str(), "crc32c")) { + return "crc32c"; } } return "unknown"; @@ -117,6 +121,10 @@ static XrdOucString convert_digest_name(const std::string &rfc_name_multiple) return "sha512"; } else if (!strcasecmp(rfc_name.c_str(), "UNIXcksum")) { return "cksum"; + } else if (!strcasecmp(rfc_name.c_str(), "crc32")) { + return "crc32"; + } else if (!strcasecmp(rfc_name.c_str(), "crc32c")) { + return "crc32c"; } } return "unknown"; @@ -137,6 +145,10 @@ static std::string convert_xrootd_to_rfc_name(const std::string &xrootd_name) return "SHA-512"; } else if (!strcasecmp(xrootd_name.c_str(), "cksum")) { return "UNIXcksum"; + } else if (!strcasecmp(xrootd_name.c_str(), "crc32")) { + return "crc32"; + } else if (!strcasecmp(xrootd_name.c_str(), "crc32c")) { + return "crc32c"; } return "unknown"; } @@ -156,6 +168,10 @@ static bool needs_base64_padding(const std::string &rfc_name) return true; } else if (!strcasecmp(rfc_name.c_str(), "UNIXcksum")) { return false; + } else if (!strcasecmp(rfc_name.c_str(), "crc32")) { + return false; + } else if (!strcasecmp(rfc_name.c_str(), "crc32c")) { + return true; } return false; } @@ -271,6 +287,8 @@ int XrdHttpReq::parseLine(char *line, int len) { } else if (!strcmp(key, "Want-Digest")) { m_req_digest.assign(val, line + len - val); trim(m_req_digest); + //Transform the user requests' want-digest to lowercase + std::transform(m_req_digest.begin(),m_req_digest.end(),m_req_digest.begin(),::tolower); } else if (!strcmp(key, "Depth")) { depth = -1; if (strcmp(val, "infinity")) @@ -1032,6 +1050,42 @@ void XrdHttpReq::mapXrdErrorToHttpStatus() { } } +/** + * Select the checksum to be computed depending on the userDigest passed in parameter + * @param userDigest the digest request from the user (extracted from the Want-Digest header) + * @param selectedChecksum the checksum that will be performed + */ +void XrdHttpReq::selectChecksum(const std::string &userDigest, std::string & selectedChecksum) { + char * configChecksumList; + selectedChecksum = "unknown"; + if((configChecksumList = getenv("XRD_CSLIST"))) { + //The env variable is set, some checksums have been configured + std::vector userDigestsVec; + XrdUtils::Utils::splitString(userDigestsVec,userDigest,","); + std::vector configChecksums; + XrdUtils::Utils::splitString(configChecksums,configChecksumList,","); + selectedChecksum = configChecksums[0]; + auto configChecksumItor = configChecksums.end(); + std::find_if(userDigestsVec.begin(), userDigestsVec.end(), [&configChecksums, &configChecksumItor](const std::string & userDigest){ + configChecksumItor = std::find_if(configChecksums.begin(),configChecksums.end(),[&userDigest](const std::string & configChecksum){ + std::string userDigestTrimmed = userDigest; + trim(userDigestTrimmed); + if(configChecksum.find(userDigestTrimmed) != std::string::npos) { + return true; + } + return false; + }); + return configChecksumItor != configChecksums.end(); + }); + //By default, the selected checksum is the first one of the configured checksum list. + // If the user gave a checksum that do not exist, then the checksum returned will be the default one + configChecksumItor = configChecksumItor != configChecksums.end() ? configChecksumItor : configChecksums.begin(); + std::vector checksumIdName; + XrdUtils::Utils::splitString(checksumIdName,*configChecksumItor,":"); + selectedChecksum = checksumIdName[1]; + } +} + int XrdHttpReq::ProcessHTTPReq() { kXR_int32 l; @@ -1105,12 +1159,15 @@ int XrdHttpReq::ProcessHTTPReq() { const char *opaque = strchr(resourceplusopaque.c_str(), '?'); // Note that doChksum requires that the memory stays alive until the callback is invoked. m_resource_with_digest = resourceplusopaque; + std::string selectedChecksum; + selectChecksum(m_req_digest,selectedChecksum); + m_req_digest = convert_digest_name(selectedChecksum).c_str(); if (!opaque) { m_resource_with_digest += "?cks.type="; - m_resource_with_digest += convert_digest_name(m_req_digest); + m_resource_with_digest += m_req_digest.c_str(); } else { m_resource_with_digest += "&cks.type="; - m_resource_with_digest += convert_digest_name(m_req_digest); + m_resource_with_digest += m_req_digest.c_str(); } if (prot->doChksum(m_resource_with_digest) < 0) { // In this case, the Want-Digest header was set and PostProcess gave the go-ahead to do a checksum. diff --git a/src/XrdHttp/XrdHttpReq.hh b/src/XrdHttp/XrdHttpReq.hh index 5cbb81ea57a..e3b13cae3a1 100644 --- a/src/XrdHttp/XrdHttpReq.hh +++ b/src/XrdHttp/XrdHttpReq.hh @@ -116,6 +116,14 @@ private: // Sanitize the resource from http[s]://[host]/ questionable prefix void sanitizeResourcePfx(); + + /** + * Select the checksum to be computed depending on the userDigest passed in parameter + * @param userDigest the digest request from the user (extracted from the Want-Digest header) + * @param selectedChecksum the checksum that will be performed + */ + void selectChecksum(const std::string & userDigest, std::string & selectedChecksum); + public: XrdHttpReq(XrdHttpProtocol *protinstance) : keepalive(true) { diff --git a/src/XrdUtils/XrdUtils.hh b/src/XrdUtils/XrdUtils.hh new file mode 100644 index 00000000000..a632669357f --- /dev/null +++ b/src/XrdUtils/XrdUtils.hh @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) +// Author: Cedric Caffy +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that 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_XRDUTILS_HH +#define XROOTD_XRDUTILS_HH + +#include + +namespace XrdUtils { + + 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 ); + } + }; + +} + +#endif //XROOTD_XRDUTILS_HH From 217c3968ca28629f638f576ee5ab9805c81eb72f Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 30 Jan 2023 17:28:11 +0100 Subject: [PATCH 160/773] Update notes on issue 1707: XrdHttp checksum logic change --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 6a55e1a63cb..35c4ba578e8 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -31,6 +31,8 @@ Prerelease Notes **Commit: 0f79a38 **[XrdMacaroons] Fix authentication when different tokens are used in the same TCP session** **Commit: 4410d56 + **[XrdHttp] 500 Internal Server Error on unknown checksum algorithm** + **Commit: 8c9fbb3 + **Miscellaneous** **[Pss]** Convert leftover print statement to debug action. From 012c5fe9318ba3ce302be30cb2134dbd3469a5d9 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 31 Jan 2023 13:56:42 +0100 Subject: [PATCH 161/773] [XrdCl] AtLeastPolicy: correctly return the status. --- 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 dd38c56d46b..6736fefbaf0 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 == failed_threshold ) res = status; // we dropped below the threshold + if( nb + 1 == 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 94fd932335375d030d7d5b19e1f0acc142d4f4cc Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 1 Feb 2023 09:33:32 +0100 Subject: [PATCH 162/773] XrdHttp: The configured checksum list is now initialized in the Configure() method of the XrdHttpProtocol --- src/XrdHttp/XrdHttpProtocol.cc | 2 ++ src/XrdHttp/XrdHttpProtocol.hh | 3 +++ src/XrdHttp/XrdHttpReq.cc | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index f7ee7b2cc91..f9b8f0dd4c4 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -108,6 +108,7 @@ XrdSysError XrdHttpProtocol::eDest = 0; // Error message handler 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; XrdSysTrace XrdHttpTrace("http"); @@ -1633,6 +1634,7 @@ int XrdHttpProtocol::Configure(char *parms, XrdProtocol_Config * pi) { BPool = pi->BPool; hailWait = 10000; readWait = 30000; + xrd_cslist = getenv("XRD_CSLIST"); Port = pi->Port; diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index 51d31c34a4a..ee4f975d841 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -415,5 +415,8 @@ protected: /// C-style vptr table for our custom BIO objects. static BIO_METHOD *m_bio_method; + + /// The list of checksums that were configured via the xrd.cksum parameter on the server config file + static char * xrd_cslist; }; #endif diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 00058e29d0f..403c1187a5c 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -1056,9 +1056,9 @@ void XrdHttpReq::mapXrdErrorToHttpStatus() { * @param selectedChecksum the checksum that will be performed */ void XrdHttpReq::selectChecksum(const std::string &userDigest, std::string & selectedChecksum) { - char * configChecksumList; + char * configChecksumList = XrdHttpProtocol::xrd_cslist; selectedChecksum = "unknown"; - if((configChecksumList = getenv("XRD_CSLIST"))) { + if(configChecksumList != nullptr) { //The env variable is set, some checksums have been configured std::vector userDigestsVec; XrdUtils::Utils::splitString(userDigestsVec,userDigest,","); From 2b4af13e832b25828e8ae1e787b12abb0ba626f4 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 1 Feb 2023 10:43:23 +0100 Subject: [PATCH 163/773] XrdOucUtils: Added the splitString() method to the XrdOucUtils class and deleted the XrdUtils directory --- src/XrdCl/XrdClUtils.hh | 4 +-- src/XrdHttp/XrdHttpReq.cc | 8 +++--- src/XrdOuc/XrdOucUtils.hh | 34 ++++++++++++++++++++-- src/XrdUtils/XrdUtils.hh | 60 --------------------------------------- 4 files changed, 38 insertions(+), 68 deletions(-) delete mode 100644 src/XrdUtils/XrdUtils.hh diff --git a/src/XrdCl/XrdClUtils.hh b/src/XrdCl/XrdClUtils.hh index 3b66115945a..3d61b5829d3 100644 --- a/src/XrdCl/XrdClUtils.hh +++ b/src/XrdCl/XrdClUtils.hh @@ -31,7 +31,7 @@ #include "XrdCl/XrdClPostMaster.hh" #include "XrdCl/XrdClXRootDTransport.hh" #include "XrdNet/XrdNetUtils.hh" -#include "XrdUtils/XrdUtils.hh" +#include "XrdOuc/XrdOucUtils.hh" #include @@ -57,7 +57,7 @@ namespace XrdCl const std::string &input, const std::string &delimiter ) { - XrdUtils::Utils::splitString(result,input,delimiter); + XrdOucUtils::splitString(result,input,delimiter); } //------------------------------------------------------------------------ diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 403c1187a5c..12f2c6fc062 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -49,12 +49,12 @@ #include "Xrd/XrdLink.hh" #include "XrdXrootd/XrdXrootdBridge.hh" #include "Xrd/XrdBuffer.hh" -#include "XrdUtils/XrdUtils.hh" #include #include #include #include #include +#include "XrdOuc/XrdOucUtils.hh" #include "XrdHttpUtils.hh" @@ -1061,9 +1061,9 @@ void XrdHttpReq::selectChecksum(const std::string &userDigest, std::string & sel if(configChecksumList != nullptr) { //The env variable is set, some checksums have been configured std::vector userDigestsVec; - XrdUtils::Utils::splitString(userDigestsVec,userDigest,","); + XrdOucUtils::splitString(userDigestsVec,userDigest,","); std::vector configChecksums; - XrdUtils::Utils::splitString(configChecksums,configChecksumList,","); + XrdOucUtils::splitString(configChecksums,configChecksumList,","); selectedChecksum = configChecksums[0]; auto configChecksumItor = configChecksums.end(); std::find_if(userDigestsVec.begin(), userDigestsVec.end(), [&configChecksums, &configChecksumItor](const std::string & userDigest){ @@ -1081,7 +1081,7 @@ void XrdHttpReq::selectChecksum(const std::string &userDigest, std::string & sel // If the user gave a checksum that do not exist, then the checksum returned will be the default one configChecksumItor = configChecksumItor != configChecksums.end() ? configChecksumItor : configChecksums.begin(); std::vector checksumIdName; - XrdUtils::Utils::splitString(checksumIdName,*configChecksumItor,":"); + XrdOucUtils::splitString(checksumIdName,*configChecksumItor,":"); selectedChecksum = checksumIdName[1]; } } diff --git a/src/XrdOuc/XrdOucUtils.hh b/src/XrdOuc/XrdOucUtils.hh index 9442bcc24c9..54844828c2d 100644 --- a/src/XrdOuc/XrdOucUtils.hh +++ b/src/XrdOuc/XrdOucUtils.hh @@ -32,6 +32,7 @@ #include #include +#include class XrdSysError; class XrdOucString; @@ -130,7 +131,36 @@ static bool PidFile(XrdSysError &eDest, const char *path); static int getModificationTime(const char * path, time_t & modificationTime); - XrdOucUtils() {} - ~XrdOucUtils() {} + //------------------------------------------------------------------------ + //! 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 ); + } + + XrdOucUtils() {} + ~XrdOucUtils() {} }; #endif diff --git a/src/XrdUtils/XrdUtils.hh b/src/XrdUtils/XrdUtils.hh deleted file mode 100644 index a632669357f..00000000000 --- a/src/XrdUtils/XrdUtils.hh +++ /dev/null @@ -1,60 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Cedric Caffy -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that 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_XRDUTILS_HH -#define XROOTD_XRDUTILS_HH - -#include - -namespace XrdUtils { - - 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 ); - } - }; - -} - -#endif //XROOTD_XRDUTILS_HH From 355bda02f6fc76fee4b318db92131cb80bfc4e94 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 1 Feb 2023 13:34:58 +0100 Subject: [PATCH 164/773] XrdOucUtils: splitString() implementation moved to .cc file --- src/XrdOuc/XrdOucUtils.cc | 30 ++++++++++++++++++++++++++++++ src/XrdOuc/XrdOucUtils.hh | 33 +++++---------------------------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/XrdOuc/XrdOucUtils.cc b/src/XrdOuc/XrdOucUtils.cc index 8d45818f5d4..73df31b1879 100644 --- a/src/XrdOuc/XrdOucUtils.cc +++ b/src/XrdOuc/XrdOucUtils.cc @@ -31,6 +31,8 @@ #include #include #include +#include +#include #ifdef WIN32 #include @@ -1398,5 +1400,33 @@ int XrdOucUtils::getModificationTime(const char *path, time_t &modificationTime) return statRet; } +template +void XrdOucUtils::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 ); + } +} + +template void XrdOucUtils::splitString>(std::vector &result, const std::string &input, const std::string &delimiter); +template void XrdOucUtils::splitString>(std::list &result, const std::string &input, const std::string &delimiter); + #endif diff --git a/src/XrdOuc/XrdOucUtils.hh b/src/XrdOuc/XrdOucUtils.hh index 54844828c2d..d609f7f3dcd 100644 --- a/src/XrdOuc/XrdOucUtils.hh +++ b/src/XrdOuc/XrdOucUtils.hh @@ -131,34 +131,11 @@ static bool PidFile(XrdSysError &eDest, const char *path); static int getModificationTime(const char * path, time_t & modificationTime); - //------------------------------------------------------------------------ - //! 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 ); - } +//------------------------------------------------------------------------ +//! Split a string +//------------------------------------------------------------------------ +template +static void splitString( Container &result, const std::string &input, const std::string &delimiter ); XrdOucUtils() {} ~XrdOucUtils() {} From c1cf1f658daa4ba27f87794da6f6850c6cfa88b9 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 1 Feb 2023 14:51:27 +0100 Subject: [PATCH 165/773] [XrdCl] xrdfs: unify rm output on error. --- src/XrdCl/XrdClFS.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClFS.cc b/src/XrdCl/XrdClFS.cc index 3e0eb992bf2..629e83c7092 100644 --- a/src/XrdCl/XrdClFS.cc +++ b/src/XrdCl/XrdClFS.cc @@ -609,7 +609,7 @@ XRootDStatus DoRm( FileSystem *fs, std::mutex mtx; }; std::shared_ptr print; - if( argc - 1 > 1 ) + if( argc - 1 > 0 ) print = std::make_shared(); std::vector rms; From 42cdc25d56e4ed223570f30f9f08a9436df4749e Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 1 Feb 2023 15:06:51 +0100 Subject: [PATCH 166/773] XrdClUtils: Added history about the splitString() method --- src/XrdCl/XrdClUtils.hh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/XrdCl/XrdClUtils.hh b/src/XrdCl/XrdClUtils.hh index 3d61b5829d3..77cd8898a31 100644 --- a/src/XrdCl/XrdClUtils.hh +++ b/src/XrdCl/XrdClUtils.hh @@ -57,6 +57,12 @@ namespace XrdCl const std::string &input, const std::string &delimiter ) { + /* + * This was done in order to not duplicate code as this method + * is also used in XrdHttp + * TODO: Maybe this method could be collapsed + * to avoid this middle-man call here + */ XrdOucUtils::splitString(result,input,delimiter); } From 549837e8c2a8fd53b1421429d2130850aa3d26ed Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 1 Feb 2023 15:08:19 +0100 Subject: [PATCH 167/773] Updated pre-release notes for the XrdHttp checksum logic change to follow the RFC --- docs/PreReleaseNotes.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 35c4ba578e8..99fd876c464 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -31,8 +31,8 @@ Prerelease Notes **Commit: 0f79a38 **[XrdMacaroons] Fix authentication when different tokens are used in the same TCP session** **Commit: 4410d56 - **[XrdHttp] 500 Internal Server Error on unknown checksum algorithm** - **Commit: 8c9fbb3 + **[XrdHttp] Follow RFC checksum requirements in XRootD http** + **Commit: TO_BE_MODIFIED + **Miscellaneous** **[Pss]** Convert leftover print statement to debug action. From fc603035d7e5043b667d40fc1781edf3aaa823ce Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 1 Feb 2023 16:49:06 +0100 Subject: [PATCH 168/773] XrdOucTUtils: Created this utility class so that it contains only templated code splitString() has been moved to this class --- src/XrdCl/XrdClUtils.hh | 4 +-- src/XrdHttp/XrdHttpReq.cc | 8 ++--- src/XrdOuc/XrdOucTUtils.hh | 71 ++++++++++++++++++++++++++++++++++++++ src/XrdOuc/XrdOucUtils.cc | 28 --------------- src/XrdOuc/XrdOucUtils.hh | 6 ---- 5 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 src/XrdOuc/XrdOucTUtils.hh diff --git a/src/XrdCl/XrdClUtils.hh b/src/XrdCl/XrdClUtils.hh index 77cd8898a31..6691196f66b 100644 --- a/src/XrdCl/XrdClUtils.hh +++ b/src/XrdCl/XrdClUtils.hh @@ -31,7 +31,7 @@ #include "XrdCl/XrdClPostMaster.hh" #include "XrdCl/XrdClXRootDTransport.hh" #include "XrdNet/XrdNetUtils.hh" -#include "XrdOuc/XrdOucUtils.hh" +#include "XrdOuc/XrdOucTUtils.hh" #include @@ -63,7 +63,7 @@ namespace XrdCl * TODO: Maybe this method could be collapsed * to avoid this middle-man call here */ - XrdOucUtils::splitString(result,input,delimiter); + XrdOucTUtils::splitString(result,input,delimiter); } //------------------------------------------------------------------------ diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 12f2c6fc062..ccd17a22781 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -54,7 +54,7 @@ #include #include #include -#include "XrdOuc/XrdOucUtils.hh" +#include "XrdOuc/XrdOucTUtils.hh" #include "XrdHttpUtils.hh" @@ -1061,9 +1061,9 @@ void XrdHttpReq::selectChecksum(const std::string &userDigest, std::string & sel if(configChecksumList != nullptr) { //The env variable is set, some checksums have been configured std::vector userDigestsVec; - XrdOucUtils::splitString(userDigestsVec,userDigest,","); + XrdOucTUtils::splitString(userDigestsVec,userDigest,","); std::vector configChecksums; - XrdOucUtils::splitString(configChecksums,configChecksumList,","); + XrdOucTUtils::splitString(configChecksums,configChecksumList,","); selectedChecksum = configChecksums[0]; auto configChecksumItor = configChecksums.end(); std::find_if(userDigestsVec.begin(), userDigestsVec.end(), [&configChecksums, &configChecksumItor](const std::string & userDigest){ @@ -1081,7 +1081,7 @@ void XrdHttpReq::selectChecksum(const std::string &userDigest, std::string & sel // If the user gave a checksum that do not exist, then the checksum returned will be the default one configChecksumItor = configChecksumItor != configChecksums.end() ? configChecksumItor : configChecksums.begin(); std::vector checksumIdName; - XrdOucUtils::splitString(checksumIdName,*configChecksumItor,":"); + XrdOucTUtils::splitString(checksumIdName,*configChecksumItor,":"); selectedChecksum = checksumIdName[1]; } } diff --git a/src/XrdOuc/XrdOucTUtils.hh b/src/XrdOuc/XrdOucTUtils.hh new file mode 100644 index 00000000000..d723a26c92f --- /dev/null +++ b/src/XrdOuc/XrdOucTUtils.hh @@ -0,0 +1,71 @@ +/******************************************************************************/ +/* */ +/* X r d O u c U t i l s . 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. */ +/******************************************************************************/ + +#ifndef XROOTD_XRDOUCTUTILS_HH +#define XROOTD_XRDOUCTUTILS_HH + +#include + +/** + * This class is created to contain template code + * for utility reason. Its purpose is basically the same as the XrdOucUtils, + * but it will only contain templated code + */ +class XrdOucTUtils { + +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); +} + +}; + +#endif //XROOTD_XRDOUCTUTILS_HH diff --git a/src/XrdOuc/XrdOucUtils.cc b/src/XrdOuc/XrdOucUtils.cc index 73df31b1879..c3b7e7f1b72 100644 --- a/src/XrdOuc/XrdOucUtils.cc +++ b/src/XrdOuc/XrdOucUtils.cc @@ -1400,33 +1400,5 @@ int XrdOucUtils::getModificationTime(const char *path, time_t &modificationTime) return statRet; } -template -void XrdOucUtils::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 ); - } -} - -template void XrdOucUtils::splitString>(std::vector &result, const std::string &input, const std::string &delimiter); -template void XrdOucUtils::splitString>(std::list &result, const std::string &input, const std::string &delimiter); - #endif diff --git a/src/XrdOuc/XrdOucUtils.hh b/src/XrdOuc/XrdOucUtils.hh index d609f7f3dcd..96c1a3a1408 100644 --- a/src/XrdOuc/XrdOucUtils.hh +++ b/src/XrdOuc/XrdOucUtils.hh @@ -131,12 +131,6 @@ static bool PidFile(XrdSysError &eDest, const char *path); static int getModificationTime(const char * path, time_t & modificationTime); -//------------------------------------------------------------------------ -//! Split a string -//------------------------------------------------------------------------ -template -static void splitString( Container &result, const std::string &input, const std::string &delimiter ); - XrdOucUtils() {} ~XrdOucUtils() {} }; From 87e854e897e69e909ffe80b029fec352deb742ee Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 1 Feb 2023 11:39:49 -0800 Subject: [PATCH 169/773] Add commit hash to checksum fix in the notes. --- docs/PreReleaseNotes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 99fd876c464..6fa303c3a29 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -21,7 +21,7 @@ Prerelease Notes + **Minor bug fixes** **[Server]** Part 1: Follow RFC for multiple chksums in http (issue 1707). - **Commit: e63fe75 + **Commit: 0fc83b4 **[XrdApps]** Fix small memory leak when checksum fails **Commit: a8ad65a **[Server]** Correct formatting when displaying the SecEntity structure. From 4ddac32f14961fe6f73bda80912d37583e121ccc Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 9 Feb 2023 10:33:09 +0100 Subject: [PATCH 170/773] [XrdCl] xrdfs stat: allow multiple files. --- docs/man/xrdfs.1 | 8 +-- src/XrdCl/XrdClFS.cc | 149 ++++++++++++++++++++++++------------------- 2 files changed, 87 insertions(+), 70 deletions(-) diff --git a/docs/man/xrdfs.1 b/docs/man/xrdfs.1 index ca4062cce94..a84c7e17b09 100644 --- a/docs/man/xrdfs.1 +++ b/docs/man/xrdfs.1 @@ -34,7 +34,7 @@ Modify permissions of the \fIpath\fR. Permission string example: .RS 3 Get directory listing. .br -\fI-l\fR stat every entry and pring long listing +\fI-l\fR stat every entry and print long listing .br \fI-u\fR print paths as URLs .br @@ -79,9 +79,9 @@ Creates a directory/tree of directories. Move path1 to path2 locally on the same server. .RE -\fBstat\fR \fI\fR +\fBstat\fR \fI\fR \fI[]\fR .RS 3 -Get info about the file or directory. +Get info about the file(s) or directory(ies). .br \fI-q\fR \fB\fR Makes xrdfs return error code 55 to the shell if the @@ -169,7 +169,7 @@ l - connection statistics .RE -\fBrm\fR \fI\fR +\fBrm\fR \fI\fR \fI[]\fR .RS 3 Remove a file. diff --git a/src/XrdCl/XrdClFS.cc b/src/XrdCl/XrdClFS.cc index 629e83c7092..a3660d078ea 100644 --- a/src/XrdCl/XrdClFS.cc +++ b/src/XrdCl/XrdClFS.cc @@ -880,7 +880,7 @@ XRootDStatus DoLocate( FileSystem *fs, //------------------------------------------------------------------------------ // Process stat query //------------------------------------------------------------------------------ -XRootDStatus ProcessStatQuery( StatInfo *info, const std::string &query ) +XRootDStatus ProcessStatQuery( StatInfo &info, const std::string &query ) { Log *log = DefaultEnv::GetLog(); @@ -927,13 +927,13 @@ XRootDStatus ProcessStatQuery( StatInfo *info, const std::string &query ) if( isOrQuery ) { for( it = queryFlags.begin(); it != queryFlags.end(); ++it ) - if( info->TestFlags( flagMap[*it] ) ) + if( info.TestFlags( flagMap[*it] ) ) return XRootDStatus(); } else { for( it = queryFlags.begin(); it != queryFlags.end(); ++it ) - if( !info->TestFlags( flagMap[*it] ) ) + if( !info.TestFlags( flagMap[*it] ) ) return XRootDStatus( stError, errResponseNegative ); } @@ -955,13 +955,13 @@ XRootDStatus DoStat( FileSystem *fs, Log *log = DefaultEnv::GetLog(); uint32_t argc = args.size(); - if( argc != 2 && argc != 4 ) + if( argc < 2 ) { log->Error( AppMsg, "Wrong number of arguments." ); return XRootDStatus( stError, errInvalidArgs ); } - std::string path; + std::vector paths; std::string query; for( uint32_t i = 1; i < args.size(); ++i ) @@ -980,84 +980,101 @@ XRootDStatus DoStat( FileSystem *fs, } } else - path = args[i]; + paths.emplace_back( args[i] ); } - std::string fullPath; - if( !BuildPath( fullPath, env, path ).IsOK() ) + std::vector stats; + std::vector, std::string>> results; + for( auto &path : paths ) { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); + std::string fullPath; + if( !BuildPath( fullPath, env, path ).IsOK() ) + { + log->Error( AppMsg, "Invalid path." ); + return XRootDStatus( stError, errInvalidArgs ); + } + std::future ftr; + stats.emplace_back( XrdCl::Stat( fs, fullPath ) >> ftr ); + results.emplace_back( std::move( ftr ), std::move( fullPath ) ); } //---------------------------------------------------------------------------- // 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; - } + XrdCl::Async( XrdCl::Parallel( stats ) ); //---------------------------------------------------------------------------- // 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; - // if extended stat information is available we can print also - // change time and access time - if( info->ExtendedFormat() ) + XrdCl::XRootDStatus st; + for( auto &tpl : results ) { - std::cout << "CTime: " << info->GetChangeTimeAsString() << std::endl; - std::cout << "ATime: " << info->GetAccessTimeAsString() << std::endl; - } - std::cout << "Flags: " << info->GetFlags() << " (" << flags << ")"; + auto &ftr = std::get<0>( tpl ); + auto &fullPath = std::get<1>( tpl ); + std::cout << std::endl; + try + { + XrdCl::StatInfo info( ftr.get() ); + 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; + // if extended stat information is available we can print also + // change time and access time + if( info.ExtendedFormat() ) + { + std::cout << "CTime: " << info.GetChangeTimeAsString() << std::endl; + std::cout << "ATime: " << info.GetAccessTimeAsString() << std::endl; + } + std::cout << "Flags: " << info.GetFlags() << " (" << flags << ")"; - // check if extended stat information is available - if( info->ExtendedFormat() ) - { - std::cout << "\nMode: " << info->GetModeAsString() << std::endl; - std::cout << "Owner: " << info->GetOwner() << std::endl; - std::cout << "Group: " << info->GetGroup(); - } + // check if extended stat information is available + if( info.ExtendedFormat() ) + { + std::cout << "\nMode: " << info.GetModeAsString() << std::endl; + std::cout << "Owner: " << info.GetOwner() << std::endl; + std::cout << "Group: " << info.GetGroup(); + } - std::cout << std::endl; - if( query.length() != 0 ) - { - st = ProcessStatQuery( info, query ); - std::cout << "Query: " << query << " " << std::endl; + std::cout << std::endl; + + if( query.length() != 0 ) + { + XRootDStatus s = ProcessStatQuery( info, query ); + if( !s.IsOK() ) + st = s; + std::cout << "Query: " << query << " " << std::endl; + } + } + catch( XrdCl::PipelineException &ex ) + { + st = ex.GetError(); + log->Error( AppMsg, "Unable stat %s: %s", fullPath.c_str(), st.ToStr().c_str() ); + } } - delete info; return st; } From f19047336036f9d1e6d5e67a1fde5ce059f72ac1 Mon Sep 17 00:00:00 2001 From: Costin Grigoras Date: Wed, 8 Feb 2023 17:30:23 +0100 Subject: [PATCH 171/773] Update XrdPosixAdmin.cc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes compilation issue on older gcc ``` /usr/src/xrootd-5.5.2/src/XrdPosix/XrdPosixAdmin.cc: In member function ‘XrdCl::URL* XrdPosixAdmin::FanOut(int&)’: /usr/src/xrootd-5.5.2/src/XrdPosix/XrdPosixAdmin.cc:70:32: error: ‘ptrdiff_t’ was not declared in this scope if (i > std::numeric_limits::max() / sizeof(XrdCl::URL)) ^ /usr/src/xrootd-5.5.2/src/XrdPosix/XrdPosixAdmin.cc:70:32: note: suggested alternatives: In file included from /usr/include/c++/5/limits:42:0, from /usr/src/xrootd-5.5.2/src/XrdPosix/XrdPosixAdmin.cc:32: /usr/include/x86_64-linux-gnu/c++/5/bits/c++config.h:197:28: note: ‘std::ptrdiff_t’ typedef __PTRDIFF_TYPE__ ptrdiff_t; ^ /usr/include/x86_64-linux-gnu/c++/5/bits/c++config.h:197:28: note: ‘std::ptrdiff_t’ /usr/src/xrootd-5.5.2/src/XrdPosix/XrdPosixAdmin.cc:70:41: error: template argument 1 is invalid if (i > std::numeric_limits::max() / sizeof(XrdCl::URL)) ^ src/CMakeFiles/XrdPosix.dir/build.make:62: recipe for target 'src/CMakeFiles/XrdPosix.dir/XrdPosix/XrdPosixAdmin.cc.o' failed make[2]: *** [src/CMakeFiles/XrdPosix.dir/XrdPosix/XrdPosixAdmin.cc.o] Error 1 ``` --- src/XrdPosix/XrdPosixAdmin.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdPosix/XrdPosixAdmin.cc b/src/XrdPosix/XrdPosixAdmin.cc index eb8ccdecd9e..3b7e9810353 100644 --- a/src/XrdPosix/XrdPosixAdmin.cc +++ b/src/XrdPosix/XrdPosixAdmin.cc @@ -30,6 +30,7 @@ #include #include +#include #include #include #include From 0a9fa5f49261445b44237dd3181e8ce4aa2fcdb2 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Fri, 10 Feb 2023 11:46:37 +0100 Subject: [PATCH 172/773] XrdHttp: Supports non-natively supported checksum in the case a user provides a checksum computation program Solves issue #1901 --- src/XrdXrootd/XrdXrootdConfig.cc | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/XrdXrootd/XrdXrootdConfig.cc b/src/XrdXrootd/XrdXrootdConfig.cc index bbe470b9667..38e42458019 100644 --- a/src/XrdXrootd/XrdXrootdConfig.cc +++ b/src/XrdXrootd/XrdXrootdConfig.cc @@ -314,23 +314,29 @@ int XrdXrootdProtocol::Configure(char *parms, XrdProtocol_Config *pi) // Check if we are going to be processing checksums locally // - if (JobCKT && JobLCL) - {XrdOucErrInfo myError("Config"); + if (JobCKT) { XrdOucString csList(1024); + XrdOucErrInfo myError("Config"); XrdOucTList *tP = JobCKTLST; int csNum = 0; - do {if (osFS->chksum(XrdSfsFileSystem::csSize,tP->text,0,myError)) - {eDest.Emsg("Config",tP->text,"checksum is not natively supported."); - return 0; - } + do { + if(JobLCL) { + // Check natively supported checksum + 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(); if (csNum) csList += ','; - csList.append(csNum); csList.append(':'); csList.append(tP->text); + csList.append(csNum); + csList.append(':'); + csList.append(tP->text); csNum++; tP = tP->next; - } while(tP); + } while (tP); if (csNum) XrdOucEnv::Export("XRD_CSLIST", csList.c_str()); - } + } // Initialiaze for AIO. If we are not in debug mode and aio is enabled then we // turn off async I/O if tghe filesystem requests it or if this is a caching From ca735652c00b1e5f04e3006e54b17aefb196bf8c Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 7 Dec 2022 17:47:16 +0100 Subject: [PATCH 173/773] [XrdCl] Refactor kXR_status response handling. --- src/XrdCl/XrdClAsyncMsgReader.hh | 137 ++++++++------------- src/XrdCl/XrdClPostMasterInterfaces.hh | 13 ++ src/XrdCl/XrdClXRootDMsgHandler.cc | 59 +++------ src/XrdCl/XrdClXRootDMsgHandler.hh | 12 +- src/XrdCl/XrdClXRootDTransport.cc | 161 ++++++++++++++++--------- src/XrdCl/XrdClXRootDTransport.hh | 15 ++- 6 files changed, 206 insertions(+), 191 deletions(-) diff --git a/src/XrdCl/XrdClAsyncMsgReader.hh b/src/XrdCl/XrdClAsyncMsgReader.hh index 1233b5744b5..cae92bfeab0 100644 --- a/src/XrdCl/XrdClAsyncMsgReader.hh +++ b/src/XrdCl/XrdClAsyncMsgReader.hh @@ -135,6 +135,7 @@ namespace XrdCl readstage = ReadRawData; continue; } + //---------------------------------------------------------------- // The next step is to read the message body //---------------------------------------------------------------- @@ -168,6 +169,23 @@ namespace XrdCl continue; } //------------------------------------------------------------------ + // kXR_status is special as it can have both body and raw data, + // handle it separately + //------------------------------------------------------------------ + case ReadMore: + { + XRootDStatus st = xrdTransport.GetMore( *inmsg, &socket ); + if( !st.IsOK() || st.code == suRetry ) + return st; + inmsgsize = inmsg->GetCursor(); + + //---------------------------------------------------------------- + // The next step is to finalize the read + //---------------------------------------------------------------- + readstage = ReadDone; + continue; + } + //------------------------------------------------------------------ // We need to call a raw message handler to get the data from the // socket //------------------------------------------------------------------ @@ -196,94 +214,38 @@ namespace XrdCl return st; inmsgsize = inmsg->GetCursor(); - //---------------------------------------------------------------- - // Now check if there are some additional raw data to be read - //---------------------------------------------------------------- - if( inhandler ) - { - //-------------------------------------------------------------- - // The next step is to finalize the read - //-------------------------------------------------------------- - readstage = ReadDone; - continue; - } - - uint16_t action = strm.InspectStatusRsp( substrmnb, - inhandler ); - - if( action & MsgHandler::Corrupted ) - return XRootDStatus( stError, errCorruptedHeader ); - - if( action & MsgHandler::Raw ) - { - //-------------------------------------------------------------- - // The next step is to read the raw data - //-------------------------------------------------------------- - readstage = ReadRawData; - continue; - } - - if( action & MsgHandler::More ) - { - //-------------------------------------------------------------- - // The next step is to read the additional data in the message - // body - //-------------------------------------------------------------- - readstage = ReadMsgBody; - continue; - } - - //---------------------------------------------------------------- - // Unless we've got a kXR_status message and no handler the - // read is done - //---------------------------------------------------------------- - ServerResponse *rsphdr = (ServerResponse *)inmsg->GetBuffer(); - if( !( action & MsgHandler::RemoveHandler ) || - rsphdr->hdr.status != kXR_status || - inmsg->GetSize() < sizeof( ServerResponseStatus ) ) - { - readstage = ReadDone; - continue; - } - - //---------------------------------------------------------------- - // There is no handler and we have a kXR_status message. If we - // have already read all the message then we're done. - //---------------------------------------------------------------- - ServerResponseStatus *rspst = (ServerResponseStatus*)inmsg->GetBuffer(); - const uint32_t hdrSize = rspst->hdr.dlen; - if( inmsg->GetSize() != hdrSize + 8 ) - { - readstage = ReadDone; - continue; - } //---------------------------------------------------------------- - // Only the header of kXR_status has been read. Unmarshall the - // header and if if there is more body data call GetBody() again. + // kXR_status response needs special handling as it can have + // either (body + raw data) or (body + additional body data) //---------------------------------------------------------------- - const uint16_t reqType = rspst->bdy.requestid + kXR_1stRequest; - st = XRootDTransport::UnMarshalStatusBody( *inmsg, reqType ); - - if( !st.IsOK() && st.code == errDataError ) + if( IsStatusRsp() ) { - log->Error( AsyncSockMsg, "[%s] Failed to unmarshall " - "corrupted status body in message 0x%x.", - strmname.c_str(), inmsg.get() ); - return XRootDStatus( stError, errCorruptedHeader ); - } - if( !st.IsOK() ) - { - log->Error( AsyncSockMsg, "[%s] Failed to unmarshall " - "status body of message 0x%x.", - strmname.c_str(), inmsg.get() ); - readstage = ReadDone; - continue; - } - if ( rspst->bdy.dlen != 0 ) - { - readstage = ReadMsgBody; - continue; + uint16_t action = strm.InspectStatusRsp( substrmnb, + inhandler ); + + if( action & MsgHandler::Corrupted ) + return XRootDStatus( stError, errCorruptedHeader ); + + if( action & MsgHandler::Raw ) + { + //-------------------------------------------------------------- + // The next step is to read the raw data + //-------------------------------------------------------------- + readstage = ReadRawData; + continue; + } + + if( action & MsgHandler::More ) + { + + //-------------------------------------------------------------- + // The next step is to read the additional data in the message + // body + //-------------------------------------------------------------- + readstage = ReadMore; + continue; + } } //---------------------------------------------------------------- @@ -342,6 +304,12 @@ namespace XrdCl return XRootDStatus(); } + inline bool IsStatusRsp() + { + ServerResponseHeader *hdr = (ServerResponseHeader*)inmsg->GetBuffer(); + return ( hdr->status == kXR_status ); + } + inline bool HasEmbeddedRsp() { ServerResponseBody_Attn *attn = (ServerResponseBody_Attn*)inmsg->GetBuffer( 8 ); @@ -356,6 +324,7 @@ namespace XrdCl ReadStart, //< the next step is to initialize the read ReadHeader, //< the next step is to read the header ReadAttn, //< the next step is to read attn action code + ReadMore, //< the next step is to read more status body ReadMsgBody, //< the next step is to read the body ReadRawData, //< the next step is to read the raw data ReadDone //< the next step is to finalize the read diff --git a/src/XrdCl/XrdClPostMasterInterfaces.hh b/src/XrdCl/XrdClPostMasterInterfaces.hh index 187a9ce581b..8e21549b8fe 100644 --- a/src/XrdCl/XrdClPostMasterInterfaces.hh +++ b/src/XrdCl/XrdClPostMasterInterfaces.hh @@ -358,6 +358,19 @@ namespace XrdCl //------------------------------------------------------------------------ virtual XRootDStatus GetBody( Message &message, Socket *socket ) = 0; + //------------------------------------------------------------------------ + //! Read more of 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 XRootDStatus GetMore( Message &message, Socket *socket ) = 0; + //------------------------------------------------------------------------ //! Initialize channel //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClXRootDMsgHandler.cc b/src/XrdCl/XrdClXRootDMsgHandler.cc index d31f050e520..81e41d21bb1 100644 --- a/src/XrdCl/XrdClXRootDMsgHandler.cc +++ b/src/XrdCl/XrdClXRootDMsgHandler.cc @@ -311,33 +311,32 @@ namespace XrdCl // Ignore malformed status response //-------------------------------------------------------------------------- if( pResponse->GetSize() < sizeof( ServerResponseStatus ) ) - return Ignore; + { + log->Error( XRootDMsg, "[%s] kXR_status: invalid message size.", pUrl.GetHostId().c_str() ); + return Corrupted; + } ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); uint16_t reqId = ntohs( req->header.requestid ); //-------------------------------------------------------------------------- // Unmarshal the status body //-------------------------------------------------------------------------- - if( !pRspStatusBodyUnMarshaled ) - { - XRootDStatus st = XRootDTransport::UnMarshalStatusBody( *pResponse, reqId ); + XRootDStatus st = XRootDTransport::UnMarshalStatusBody( *pResponse, reqId ); - if( !st.IsOK() && st.code == errDataError ) - { - log->Error( XRootDMsg, "[%s] %s", pUrl.GetHostId().c_str(), - st.GetErrorMessage().c_str() ); - return Corrupted; - } + if( !st.IsOK() && st.code == errDataError ) + { + log->Error( XRootDMsg, "[%s] %s", pUrl.GetHostId().c_str(), + st.GetErrorMessage().c_str() ); + return Corrupted; + } - if( !st.IsOK() ) - { - log->Error( XRootDMsg, "[%s] Failed to unmarshall status body.", - pUrl.GetHostId().c_str() ); - pStatus = st; - HandleRspOrQueue(); - return Ignore; - } - pRspStatusBodyUnMarshaled = true; + if( !st.IsOK() ) + { + log->Error( XRootDMsg, "[%s] Failed to unmarshall status body.", + pUrl.GetHostId().c_str() ); + pStatus = st; + HandleRspOrQueue(); + return Ignore; } //-------------------------------------------------------------------------- @@ -377,27 +376,6 @@ namespace XrdCl if( size_t( sizeof( ServerResponseHeader ) + rspst->status.hdr.dlen + rspst->status.bdy.dlen ) > pResponse->GetCursor() ) action |= More; - // if we already have this data we need to unmarshal it - else if( !pRspPgWrtRetrnsmReqUnMarshalled ) - { - XRootDStatus st = XRootDTransport::UnMarchalStatusCSE( *pResponse ); - if( !st.IsOK() && st.code == errDataError ) - { - log->Error( XRootDMsg, "[%s] %s", pUrl.GetHostId().c_str(), - st.GetErrorMessage().c_str() ); - return Corrupted; - } - - if( !st.IsOK() ) - { - log->Error( XRootDMsg, "[%s] Failed to unmarshall status body.", - pUrl.GetHostId().c_str() ); - pStatus = st; - HandleRspOrQueue(); - return Ignore; - } - pRspPgWrtRetrnsmReqUnMarshalled = true; - } } return action; @@ -1126,7 +1104,6 @@ namespace XrdCl void XRootDMsgHandler::PartialReceived() { pTimeoutFence.store( false, std::memory_order_relaxed ); // Take down the timeout fence - pRspStatusBodyUnMarshaled = false; } //---------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClXRootDMsgHandler.hh b/src/XrdCl/XrdClXRootDMsgHandler.hh index ea9c35176a8..3a344e66c51 100644 --- a/src/XrdCl/XrdClXRootDMsgHandler.hh +++ b/src/XrdCl/XrdClXRootDMsgHandler.hh @@ -175,10 +175,7 @@ namespace XrdCl pCV( 0 ), - pSslErrCnt( 0 ), - - pRspStatusBodyUnMarshaled( false ), - pRspPgWrtRetrnsmReqUnMarshalled( false ) + pSslErrCnt( 0 ) { pPostMaster = DefaultEnv::GetPostMaster(); if( msg->GetSessionId() ) @@ -670,13 +667,6 @@ namespace XrdCl // Count of consecutive `errTlsSslError` errors //------------------------------------------------------------------------ size_t pSslErrCnt; - - //------------------------------------------------------------------------ - // Keep track if respective parts of kXR_status response have been - // unmarshaled. - //------------------------------------------------------------------------ - bool pRspStatusBodyUnMarshaled; - bool pRspPgWrtRetrnsmReqUnMarshalled; }; } diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc index aa0d20e22a4..f869327aa33 100644 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ b/src/XrdCl/XrdClXRootDTransport.cc @@ -348,34 +348,59 @@ namespace XrdCl { //-------------------------------------------------------------------------- // Retrieve the body - // In case of non kXR_status responses we read all the response, including - // data. For kXR_status responses we first read only the remainder of the - // header. The header must then be unmarshalled, and then a second call of - // GetBody (repeated for suRetry as needed) will read the data. //-------------------------------------------------------------------------- size_t leftToBeRead = 0; uint32_t bodySize = 0; ServerResponseHeader* rsphdr = (ServerResponseHeader*)message.GetBuffer(); bodySize = rsphdr->dlen; - if( rsphdr->status == kXR_status ) + + if( message.GetSize() < bodySize + 8 ) + message.ReAllocate( bodySize + 8 ); + + leftToBeRead = bodySize-(message.GetCursor()-8); + while( leftToBeRead ) { - if( message.GetCursor() >= bodySize+8 ) // we read everything up to the data[] - { - const size_t stlen = sizeof( ServerResponseStatus ); - if( bodySize+8 < stlen ) - return XRootDStatus( stError, errInvalidMessage, 0, - "kXR_status: invalid message size." ); + int bytesRead = 0; + XRootDStatus status = socket->Read( message.GetBufferAtCursor(), leftToBeRead, bytesRead ); - ServerResponseStatus *rspst = (ServerResponseStatus*)message.GetBuffer(); - bodySize = rspst->hdr.dlen + rspst->bdy.dlen; - } + if( !status.IsOK() || status.code == suRetry ) + return status; + leftToBeRead -= bytesRead; + message.AdvanceCursor( bytesRead ); } + return XRootDStatus( stOK, suDone ); + } + + //---------------------------------------------------------------------------- + // Read more of the message body from socket + //---------------------------------------------------------------------------- + XRootDStatus XRootDTransport::GetMore( Message &message, Socket *socket ) + { + ServerResponseHeader* rsphdr = (ServerResponseHeader*)message.GetBuffer(); + if( rsphdr->status != kXR_status ) + return XRootDStatus( stError, errInvalidOp ); + + //-------------------------------------------------------------------------- + // In case of non kXR_status responses we read all the response, including + // data. For kXR_status responses we first read only the remainder of the + // header. The header must then be unmarshalled, and then a second call to + // GetMore (repeated for suRetry as needed) will read the data. + //-------------------------------------------------------------------------- + + uint32_t bodySize = rsphdr->dlen; + if( bodySize+8 < sizeof( ServerResponseStatus ) ) + return XRootDStatus( stError, errInvalidMessage, 0, + "kXR_status: invalid message size." ); + + ServerResponseStatus *rspst = (ServerResponseStatus*)message.GetBuffer(); + bodySize += rspst->bdy.dlen; + if( message.GetSize() < bodySize + 8 ) message.ReAllocate( bodySize + 8 ); - leftToBeRead = bodySize-(message.GetCursor()-8); + size_t leftToBeRead = bodySize-(message.GetCursor()-8); while( leftToBeRead ) { int bytesRead = 0; @@ -388,6 +413,23 @@ namespace XrdCl message.AdvanceCursor( bytesRead ); } + // Unmarchal to message body + Log *log = DefaultEnv::GetLog(); + XRootDStatus st = XRootDTransport::UnMarchalStatusMore( message ); + if( !st.IsOK() && st.code == errDataError ) + { + log->Error( XRootDTransportMsg, "[msg: 0x%x] %s", &message, + st.GetErrorMessage().c_str() ); + return st; + } + + if( !st.IsOK() ) + { + log->Error( XRootDTransportMsg, "[msg: 0x%x] Failed to unmarshall status body.", + &message ); + return st; + } + return XRootDStatus( stOK, suDone ); } @@ -1334,53 +1376,64 @@ namespace XrdCl return XRootDStatus(); } - //---------------------------------------------------------------------------- - // Unmarshall the correction-segment of the status response for pgwrite - //---------------------------------------------------------------------------- - XRootDStatus XRootDTransport::UnMarchalStatusCSE( Message &msg ) + XRootDStatus XRootDTransport::UnMarchalStatusMore( Message &msg ) { ServerResponseV2 *rsp = (ServerResponseV2*)msg.GetBuffer(); - //-------------------------------------------------------------------------- - // If there's no additional data there's nothing to unmarshal - //-------------------------------------------------------------------------- - if( rsp->status.bdy.dlen == 0 ) return XRootDStatus(); - //-------------------------------------------------------------------------- - // If there's not enough data to form correction-segment report an error - //-------------------------------------------------------------------------- - if( size_t( rsp->status.bdy.dlen ) < sizeof( ServerResponseBody_pgWrCSE ) ) - return XRootDStatus( stError, errInvalidMessage, 0, - "kXR_status: invalid message size." ); + uint16_t reqType = rsp->status.bdy.requestid + kXR_1stRequest; - //-------------------------------------------------------------------------- - // Calculate the crc32c for the additional data - //-------------------------------------------------------------------------- - ServerResponseBody_pgWrCSE *cse = (ServerResponseBody_pgWrCSE*)msg.GetBuffer( sizeof( ServerResponseV2 ) ); - cse->cseCRC = ntohl( cse->cseCRC ); - size_t length = rsp->status.bdy.dlen - sizeof( uint32_t ); - void* buffer = msg.GetBuffer( sizeof( ServerResponseV2 ) + sizeof( uint32_t ) ); - uint32_t crcval = XrdOucCRC::Calc32C( buffer, length ); - - //-------------------------------------------------------------------------- - // Do the integrity checks - //-------------------------------------------------------------------------- - if( crcval != cse->cseCRC ) + switch( reqType ) { - return XRootDStatus( stError, errDataError, 0, "kXR_status response header " - "corrupted (crc32c integrity check failed)." ); - } + case kXR_pgwrite: + { + //-------------------------------------------------------------------------- + // If there's no additional data there's nothing to unmarshal + //-------------------------------------------------------------------------- + if( rsp->status.bdy.dlen == 0 ) return XRootDStatus(); + //-------------------------------------------------------------------------- + // If there's not enough data to form correction-segment report an error + //-------------------------------------------------------------------------- + if( size_t( rsp->status.bdy.dlen ) < sizeof( ServerResponseBody_pgWrCSE ) ) + return XRootDStatus( stError, errInvalidMessage, 0, + "kXR_status: invalid message size." ); + + //-------------------------------------------------------------------------- + // Calculate the crc32c for the additional data + //-------------------------------------------------------------------------- + ServerResponseBody_pgWrCSE *cse = (ServerResponseBody_pgWrCSE*)msg.GetBuffer( sizeof( ServerResponseV2 ) ); + cse->cseCRC = ntohl( cse->cseCRC ); + size_t length = rsp->status.bdy.dlen - sizeof( uint32_t ); + void* buffer = msg.GetBuffer( sizeof( ServerResponseV2 ) + sizeof( uint32_t ) ); + uint32_t crcval = XrdOucCRC::Calc32C( buffer, length ); + + //-------------------------------------------------------------------------- + // Do the integrity checks + //-------------------------------------------------------------------------- + if( crcval != cse->cseCRC ) + { + return XRootDStatus( stError, errDataError, 0, "kXR_status response header " + "corrupted (crc32c integrity check failed)." ); + } - cse->dlFirst = ntohs( cse->dlFirst ); - cse->dlLast = ntohs( cse->dlLast ); + cse->dlFirst = ntohs( cse->dlFirst ); + cse->dlLast = ntohs( cse->dlLast ); - size_t pgcnt = ( rsp->status.bdy.dlen - sizeof( ServerResponseBody_pgWrCSE ) ) / - sizeof( kXR_int64 ); - kXR_int64 *pgoffs = (kXR_int64*)msg.GetBuffer( sizeof( ServerResponseV2 ) + - sizeof( ServerResponseBody_pgWrCSE ) ); + size_t pgcnt = ( rsp->status.bdy.dlen - sizeof( ServerResponseBody_pgWrCSE ) ) / + sizeof( kXR_int64 ); + kXR_int64 *pgoffs = (kXR_int64*)msg.GetBuffer( sizeof( ServerResponseV2 ) + + sizeof( ServerResponseBody_pgWrCSE ) ); - for( size_t i = 0; i < pgcnt; ++i ) - pgoffs[i] = ntohll( pgoffs[i] ); + for( size_t i = 0; i < pgcnt; ++i ) + pgoffs[i] = ntohll( pgoffs[i] ); - return XRootDStatus(); + return XRootDStatus(); + break; + } + + default: + break; + } + + return XRootDStatus( stError, errNotSupported ); } //---------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClXRootDTransport.hh b/src/XrdCl/XrdClXRootDTransport.hh index f66a68f7c5c..03386cb677b 100644 --- a/src/XrdCl/XrdClXRootDTransport.hh +++ b/src/XrdCl/XrdClXRootDTransport.hh @@ -83,6 +83,19 @@ namespace XrdCl //------------------------------------------------------------------------ virtual XRootDStatus GetBody( Message &message, Socket *socket ); + //------------------------------------------------------------------------ + //! Read more of 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 XRootDStatus GetMore( Message &message, Socket *socket ); + //------------------------------------------------------------------------ //! Initialize channel //------------------------------------------------------------------------ @@ -190,7 +203,7 @@ namespace XrdCl //------------------------------------------------------------------------ //! Unmarshall the correction-segment of the status response for pgwrite //------------------------------------------------------------------------ - static XRootDStatus UnMarchalStatusCSE( Message &msg ); + static XRootDStatus UnMarchalStatusMore( Message &msg ); //------------------------------------------------------------------------ //! Unmarshall the header incoming message From 7daaa2c076d16aa9bd4e0262c74777bb11a7cb1e Mon Sep 17 00:00:00 2001 From: Matthew Feickert Date: Tue, 14 Feb 2023 14:58:35 -0600 Subject: [PATCH 174/773] fix: Avoid 'may be used uninitialized' warning * Initialize all variables to avoid a potential 'may be used uninitialized' warning that will become an error with `-flto=auto` is used. Co-authored-by: Adrian Sevcenco --- src/XrdCms/XrdCmsRRQ.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCms/XrdCmsRRQ.cc b/src/XrdCms/XrdCmsRRQ.cc index 3de1d17d02a..c5d925d8561 100644 --- a/src/XrdCms/XrdCmsRRQ.cc +++ b/src/XrdCms/XrdCmsRRQ.cc @@ -360,7 +360,7 @@ void XrdCmsRRQ::sendRedResp(XrdCmsRRQSlot *rP) // EPNAME("sendRedResp"); static const int ovhd = sizeof(kXR_unt32); XrdCmsNode *nP; - int doredir, port, hlen = 0; + int doredir = 0, port = 0, hlen = 0; // Determine where the client should be redirected // From 9d2973d1b764609872d3f953aef1238366ea50fe Mon Sep 17 00:00:00 2001 From: Matevz Tadel Date: Tue, 14 Feb 2023 14:16:12 -0800 Subject: [PATCH 175/773] [Pfc] Add missing if-error for a rare direct-read error trace. --- src/XrdPfc/XrdPfcFile.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XrdPfc/XrdPfcFile.cc b/src/XrdPfc/XrdPfcFile.cc index ab51412c4f4..d438c56b43e 100644 --- a/src/XrdPfc/XrdPfcFile.cc +++ b/src/XrdPfc/XrdPfcFile.cc @@ -1164,7 +1164,8 @@ void File::ProcessDirectReadFinished(ReadRequest *rreq, int bytes_read, int erro // Called from DirectResponseHandler. // NOT under lock. - TRACEF(Error, "Read(), direct read finished with error " << -error_cond << " " << XrdSysE2T(-error_cond)); + if (error_cond) + TRACEF(Error, "Read(), direct read finished with error " << -error_cond << " " << XrdSysE2T(-error_cond)); m_state_cond.Lock(); From e7bccb46329a10909149bea1906bdcf95467f1ba Mon Sep 17 00:00:00 2001 From: Matevz Tadel Date: Tue, 14 Feb 2023 15:05:06 -0800 Subject: [PATCH 176/773] Update PreReleaseNotes.txt --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 33572e5648a..dea9b140268 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -16,5 +16,7 @@ Prerelease Notes + **Major bug fixes** + **Minor bug fixes** + **[PFC]** Add a missing if-error for a rare direct-read error trace. + **Commit: 9d2973d1 + **Miscellaneous** From b2dc65716d7650640ceb142b57c58a5df128cced Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 14 Feb 2023 11:20:41 +0100 Subject: [PATCH 177/773] [Server] Avoid a race condition during deferred file close --- src/XrdXrootd/XrdXrootdXeq.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index c4e0619ab3a..1ca6cf79d83 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -557,14 +557,24 @@ int XrdXrootdProtocol::do_Close() fp->cbArg = ReqID.getID(); fp->XrdSfsp->error.setErrCB(&closeCB, (unsigned long long)fp); +// Add a reference count to the file in case the close will be deferred. In +// the deferred case the reference is used to prevent the callback from +// deleting the file until we have done necessary processing of the object +// during its removal from the open table. +// + fp->Ref(1); + // Do an explicit close of the file here; check for exceptions. Stall requests // leave the file open as there will be a retry. Otherwise, we remove the // file from our open table but a "started" return defers the the delete. // rc = fp->XrdSfsp->close(); TRACEP(FS, " fh=" < Date: Tue, 14 Feb 2023 01:34:36 -0600 Subject: [PATCH 180/773] [CMake] Use CMake 3.12+ FindPython to get Python components * Use FindPython from CMake 3.12+ which relies on find_package advancements to select the components that are required. Select the 'Interpreter' and 'Development' components to find the required executables and files. - c.f. https://cmake.org/cmake/help/v3.16/module/FindPython.html * Modern FindPython is capitalization sensitive and will set the following variables that are used throughout the project. * Python_FOUND * Python_Interpreter_FOUND * Python_EXECUTABLE * Python_Development_FOUND - c.f. https://cmake.org/cmake/help/v3.12/module/FindPython.html#result-variables --- .github/workflows/build.yml | 14 +++++++------- bindings/python/CMakeLists.txt | 12 ++++++------ cmake/XRootDFindLibs.cmake | 9 +++------ cmake/XRootDSummary.cmake | 2 +- packaging/debian/rules | 2 +- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bfc562a1d06..487aac2a16a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,7 +76,7 @@ jobs: -DCMAKE_CXX_STANDARD=17 \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_INSTALL_PREFIX=/usr \ - -DPYTHON_EXECUTABLE=$(command -v python3) \ + -DPython_EXECUTABLE=$(command -v python3) \ -DFORCE_ENABLED=ON \ -DENABLE_TESTS=ON \ -DENABLE_XRDEC=ON \ @@ -167,7 +167,7 @@ jobs: -DCMAKE_CXX_STANDARD=17 \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_INSTALL_PREFIX=/usr \ - -DPYTHON_EXECUTABLE=$(command -v python3) \ + -DPython_EXECUTABLE=$(command -v python3) \ -DFORCE_ENABLED=ON \ -DENABLE_TESTS=ON \ -DENABLE_XRDEC=ON \ @@ -245,7 +245,7 @@ jobs: cd .. cmake3 \ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ - -DPYTHON_EXECUTABLE=$(command -v python3) \ + -DPython_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ -DPIP_OPTIONS="--verbose" \ -S xrootd \ @@ -322,7 +322,7 @@ jobs: cd .. cmake3 \ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ - -DPYTHON_EXECUTABLE=$(command -v python3) \ + -DPython_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ -DPIP_OPTIONS="--verbose --force-reinstall --prefix /usr/local/" \ -S xrootd \ @@ -391,7 +391,7 @@ jobs: cd .. cmake3 \ -DCMAKE_INSTALL_PREFIX=/usr/ \ - -DPYTHON_EXECUTABLE=$(command -v python2) \ + -DPython_EXECUTABLE=$(command -v python2) \ -DENABLE_TESTS=ON \ -DPIP_OPTIONS="--verbose" \ -DXRD_PYTHON_REQ_VERSION="2.7" \ @@ -453,7 +453,7 @@ jobs: cd .. cmake \ -DCMAKE_INSTALL_PREFIX=/usr \ - -DPYTHON_EXECUTABLE=$(command -v python3) \ + -DPython_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ -DPIP_OPTIONS="--verbose" \ -S xrootd \ @@ -517,7 +517,7 @@ jobs: -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ -DCMAKE_PREFIX_PATH=/usr/local/opt/openssl@3 \ - -DPYTHON_EXECUTABLE=$(command -v python3) \ + -DPython_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ -DPIP_OPTIONS="--verbose" \ -S xrootd \ diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 739aaae6c67..2c5168475f4 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -45,7 +45,7 @@ endif() # Check it the Python interpreter has a valid version of pip execute_process( - COMMAND ${PYTHON_EXECUTABLE} -m pip --version + COMMAND ${Python_EXECUTABLE} -m pip --version RESULT_VARIABLE VALID_PIP_EXIT_CODE OUTPUT_QUIET ) @@ -53,18 +53,18 @@ execute_process( 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." + " ${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`." + " 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 + COMMAND ${Python_EXECUTABLE} ${SETUP_PY} --verbose build DEPENDS ${DEPS}) add_custom_target(python_target ALL DEPENDS ${OUTPUT} XrdCl) @@ -84,7 +84,7 @@ if ( NOT ${VALID_PIP_EXIT_CODE} EQUAL 0 ) CODE "EXECUTE_PROCESS( RESULT_VARIABLE INSTALL_STATUS - COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${PYTHON_EXECUTABLE} ${SETUP_PY} install \ + COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${Python_EXECUTABLE} ${SETUP_PY} install \ --verbose \ --prefix \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX} \ ${DEB_INSTALL_ARGS} @@ -98,7 +98,7 @@ else() CODE "EXECUTE_PROCESS( RESULT_VARIABLE INSTALL_STATUS - COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${PYTHON_EXECUTABLE} -m pip install \ + COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${Python_EXECUTABLE} -m pip install \ ${PIP_OPTIONS} \ ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index d9bb661ae3d..f88e871c983 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -178,15 +178,12 @@ endif() if( ENABLE_PYTHON AND (LINUX OR KFREEBSD OR Hurd OR MacOSX) ) if( FORCE_ENABLED ) - find_package( PythonInterp ${XRD_PYTHON_REQ_VERSION} REQUIRED ) - find_package( PythonLibs ${XRD_PYTHON_REQ_VERSION} REQUIRED ) + find_package( Python ${XRD_PYTHON_REQ_VERSION} COMPONENTS Interpreter Development REQUIRED ) else() - find_package( PythonInterp ${XRD_PYTHON_REQ_VERSION} ) - find_package( PythonLibs ${XRD_PYTHON_REQ_VERSION} ) + find_package( Python ${XRD_PYTHON_REQ_VERSION} COMPONENTS Interpreter Development ) endif() - if( PYTHONINTERP_FOUND AND PYTHONLIBS_FOUND ) + if( Python_Interpreter_FOUND AND Python_Development_FOUND ) set( BUILD_PYTHON TRUE ) - set( PYTHON_FOUND TRUE ) else() set( BUILD_PYTHON FALSE ) endif() diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index ca405350764..7874756f91d 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -12,7 +12,7 @@ component_status( TESTS BUILD_TESTS CPPUNIT_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_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 ) diff --git a/packaging/debian/rules b/packaging/debian/rules index 7e9582df484..44203c03974 100755 --- a/packaging/debian/rules +++ b/packaging/debian/rules @@ -6,7 +6,7 @@ export PYBUILD_NAME=xrootd dh $@ --builddirectory=build --destdir=deb_packages --with python3 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 override_dh_install: install -D -m 644 packaging/common/client.conf deb_packages/etc/xrootd/client.conf From f0353f87c4969d02c466b111303337d94e8cb053 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 16 Feb 2023 12:19:39 +0100 Subject: [PATCH 181/773] [XrdSys] Add utility class for handling shared memory, v2. --- src/XrdSys/XrdSysSharedMemory.hh | 2 +- src/XrdSys/XrdSysShmem.hh | 281 +++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 src/XrdSys/XrdSysShmem.hh diff --git a/src/XrdSys/XrdSysSharedMemory.hh b/src/XrdSys/XrdSysSharedMemory.hh index 6b3cc12a655..19a0119af55 100644 --- a/src/XrdSys/XrdSysSharedMemory.hh +++ b/src/XrdSys/XrdSysSharedMemory.hh @@ -68,7 +68,7 @@ namespace XrdSys int Create( size_t size ) { this->size = size; - fd = shm_open( name.c_str(), O_CREAT | O_EXCL | O_RDWR, 0600 ); + fd = shm_open( name.c_str(), O_CREAT | O_RDWR, 0600 ); if( fd < 0 ) return -1; if( ftruncate( fd, size ) < 0 ) diff --git a/src/XrdSys/XrdSysShmem.hh b/src/XrdSys/XrdSysShmem.hh new file mode 100644 index 00000000000..35a4ff624b4 --- /dev/null +++ b/src/XrdSys/XrdSysShmem.hh @@ -0,0 +1,281 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2013 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_XRDSYS_XRDSYSSHMEM_HH_ +#define SRC_XRDSYS_XRDSYSSHMEM_HH_ + +#include +#include + +#include +#include +#include +#include + +namespace XrdSys +{ + /** + * Shared memory exception type. + */ + struct shm_error : public std::exception + { + shm_error( int errcode ) : + errcode( errcode ), errmsg( strerror( errcode )){ } + + const int errcode; //> errno + const std::string errmsg; //> error message + }; + + /** + * Smart pointer for encapsulating shared memory segments. + * The destructor will automatically unmap the memory from + * user's virtual address space. + * + * It is user responsibility to finalize the T object + * (the shm_ptr does not ensure calling a destructor + * for the object). + */ + template + class shm_ptr + { + /** + * Friend factory methods + */ + template + friend shm_ptr make_shm( const std::string&, Args... ); + template + friend shm_ptr make_shm( const std::string& ); + template + friend shm_ptr get_shm( const std::string& ); + + public: + /** + * Move constructor + */ + shm_ptr( shm_ptr &&sp ) : ptr( sp.ptr ) + { + sp.ptr = nullptr; + } + + /** + * Destructor + */ + ~shm_ptr() + { + if( ptr ) + munmap( ptr, sizeof( T ) ); + } + + /** + * Member access operator + */ + T* operator->() + { + return ptr; + } + + /** + * Member access operator (const) + */ + const T* operator->() const + { + return ptr; + } + + /** + * Dereferencing operator + */ + T& operator*() + { + return *ptr; + } + + /** + * Dereferencing operator (const) + */ + const T& operator*() const + { + return *ptr; + } + + private: + + /** + * Helper function for mapping the shared memory block + * into user virtual address space + * + * @param fd : handle to the shared memory segment + * @param size : size of the shared memory segment + * @return : pointer to the shared memory + */ + inline static void* map_shm( int fd, size_t size ) + { + void *mem = mmap( nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); + if( mem == MAP_FAILED ) + { + mem = nullptr; + throw shm_error( errno ); + } + return mem; + } + + /** + * Helper function for creating shared memory block + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @param size : size of the shared memory segment + * @return : pointer to the shared memory + */ + inline static void* create_shm( const std::string &name, size_t size ) + { + int fd = shm_open( name.c_str(), O_CREAT | O_RDWR, 0600 ); + if( fd < 0 ) + throw shm_error( errno ); + if( ftruncate( fd, size ) < 0 ) + throw shm_error( errno ); + void *mem = map_shm( fd, size ); + close( fd ); + return mem; + } + + /** + * Helper function for initializing existing shared memory block + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @return : pointer to the shared memory + */ + inline static std::tuple init_shm( const std::string &name ) + { + int fd = shm_open( name.c_str(), O_RDWR, 0600 ); + if( fd < 0 ) + throw shm_error( errno ); + struct stat statbuf; + if( fstat( fd, &statbuf ) < 0 ) + throw shm_error( errno ); + size_t size = statbuf.st_size; + void *mem = map_shm( fd, size ); + close( fd ); + return std::make_tuple( mem, size ); + } + + /** + * Constructor. Maps shared memory block identified by + * name argument into user virtual address space and + * constructs within that memory object of type T using + * arguments args. + */ + template + shm_ptr( void *mem, Args... args ) : ptr( new( mem ) T( std::forward( args... ) ) ) + { + } + + /** + * Constructor. Maps shared memory block identified by + * name argument into user virtual address space and + * constructs within that memory object of type T using + * default constructor. + */ + shm_ptr( void *mem ) : ptr( new( mem ) T() ) + { + } + + /** + * Constructor. Takes ownership of a memory block. + */ + shm_ptr( T *mem ) : ptr( mem ){} + + T *ptr; + }; + + /** + * Factory for creating a shared memory block. + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @param args : argument to be passed to constructor of + * type T + * @return : pointer to the shared memory + * @throws : an instance of shm_error in case of failure + */ + template + shm_ptr make_shm( const std::string &name, Args... args ) + { + void *mem = shm_ptr::create_shm( name, sizeof( T ) ); + return shm_ptr( mem, std::forward( args... ) ); + } + + /** + * Factory for creating a shared memory block. + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @return : pointer to the shared memory + * @throws : an instance of shm_error in case of failure + */ + template + shm_ptr make_shm( const std::string &name ) + { + void *mem = shm_ptr::create_shm( name, sizeof( T ) ); + return shm_ptr( mem ); + } + + /** + * Factory for getting an existing shared memory block. + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @return : pointer to the shared memory + * @throws : an instance of shm_error in case of failure + */ + template + shm_ptr get_shm( const std::string &name ) + { + auto tpl = shm_ptr::init_shm( name ); + if( sizeof( T ) > std::get<1>( tpl ) ) + throw shm_error( EINVAL ); + return shm_ptr( reinterpret_cast( std::get<0>( tpl ) ) ); + } + + /** + * Helper function for deleting an existing shared memory block. + * @throws : an instance of shm_error in case of failure + */ + inline void rm_shm( const std::string &name ) + { + int rc = shm_unlink( name.c_str() ); + if( rc < 0 ) + throw shm_error( errno ); + } +} + + + +#endif /* SRC_XRDSYS_XRDSYSSHMEM_HH_ */ From 1e70bd4ec58fea89dbe405bfe04918efdcf03c8c Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 16 Feb 2023 17:17:53 +0100 Subject: [PATCH 182/773] [XrdSys] Simplify shm_ptr implementation. --- src/XrdSys/XrdSysShmem.hh | 160 ++++++++++++++++++-------------------- 1 file changed, 74 insertions(+), 86 deletions(-) diff --git a/src/XrdSys/XrdSysShmem.hh b/src/XrdSys/XrdSysShmem.hh index 35a4ff624b4..d5c681f457c 100644 --- a/src/XrdSys/XrdSysShmem.hh +++ b/src/XrdSys/XrdSysShmem.hh @@ -59,9 +59,7 @@ namespace XrdSys template class shm_ptr { - /** - * Friend factory methods - */ + /// Friend factory methods template friend shm_ptr make_shm( const std::string&, Args... ); template @@ -70,55 +68,93 @@ namespace XrdSys friend shm_ptr get_shm( const std::string& ); public: + /** - * Move constructor + * Constructor. Creates shared memory block (of size at least + * size) identified by name argument and maps it into user + * virtual address space and constructs within that memory + * object of type T using arguments args. */ - shm_ptr( shm_ptr &&sp ) : ptr( sp.ptr ) + template + shm_ptr( const std::string &name, size_t size, Args&&... args ) { - sp.ptr = nullptr; + void *mem = nullptr; + std::tie( mem, size ) = create_shm( name, size ); + ptr = new( mem ) T( std::forward( args... ) ); } /** - * Destructor + * Constructor. Creates shared memory block (of size at least + * size) identified by name argument and maps it into user + * virtual address space and constructs within that memory + * object of type T using default constructor. */ - ~shm_ptr() + shm_ptr( const std::string &name, size_t size ) { - if( ptr ) - munmap( ptr, sizeof( T ) ); + void *mem = nullptr; + std::tie( mem, size ) = create_shm( name, size ); + ptr = new( mem ) T(); } /** - * Member access operator + * Constructor for initializing from existing shared memory block. + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) */ - T* operator->() + shm_ptr( const std::string &name ) { - return ptr; + int fd = shm_open( name.c_str(), O_RDWR, 0600 ); + if( fd < 0 ) + throw shm_error( errno ); + struct stat statbuf; + if( fstat( fd, &statbuf ) < 0 ) + throw shm_error( errno ); + size = statbuf.st_size; + if( sizeof( T ) > size ) + throw shm_error( EINVAL ); + void *mem = map_shm( fd, size ); + close( fd ); + ptr = reinterpret_cast( mem ); } - /** - * Member access operator (const) - */ - const T* operator->() const + /// Move constructor + shm_ptr( shm_ptr &&mv ) : ptr( mv.ptr ), size( mv.size ) { - return ptr; + mv.ptr = nullptr; + mv.size = 0; } - /** - * Dereferencing operator - */ - T& operator*() + /// Move assignment operator + shm_ptr& operator=( shm_ptr &&mv ) { - return *ptr; + ptr = mv.ptr; + size = mv.size; + mv.ptr = nullptr; + mv.size = 0; + return *this; } - /** - * Dereferencing operator (const) - */ - const T& operator*() const + /// Destructor + ~shm_ptr() { - return *ptr; + if( ptr ) + munmap( ptr, size ); } + /// Member access operator + T* operator->() { return ptr; } + + /// Member access operator (const) + const T* operator->() const { return ptr; } + + /// Dereferencing operator + T& operator*() { return *ptr; } + + /// Dereferencing operator (const) + const T& operator*() const { return *ptr; } + private: /** @@ -149,67 +185,24 @@ namespace XrdSys * @param size : size of the shared memory segment * @return : pointer to the shared memory */ - inline static void* create_shm( const std::string &name, size_t size ) + inline static std::tuple create_shm( const std::string &name, size_t size ) { int fd = shm_open( name.c_str(), O_CREAT | O_RDWR, 0600 ); if( fd < 0 ) throw shm_error( errno ); if( ftruncate( fd, size ) < 0 ) throw shm_error( errno ); - void *mem = map_shm( fd, size ); - close( fd ); - return mem; - } - - /** - * Helper function for initializing existing shared memory block - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - * @return : pointer to the shared memory - */ - inline static std::tuple init_shm( const std::string &name ) - { - int fd = shm_open( name.c_str(), O_RDWR, 0600 ); - if( fd < 0 ) - throw shm_error( errno ); struct stat statbuf; if( fstat( fd, &statbuf ) < 0 ) throw shm_error( errno ); - size_t size = statbuf.st_size; + size = statbuf.st_size; void *mem = map_shm( fd, size ); close( fd ); return std::make_tuple( mem, size ); } - /** - * Constructor. Maps shared memory block identified by - * name argument into user virtual address space and - * constructs within that memory object of type T using - * arguments args. - */ - template - shm_ptr( void *mem, Args... args ) : ptr( new( mem ) T( std::forward( args... ) ) ) - { - } - - /** - * Constructor. Maps shared memory block identified by - * name argument into user virtual address space and - * constructs within that memory object of type T using - * default constructor. - */ - shm_ptr( void *mem ) : ptr( new( mem ) T() ) - { - } - - /** - * Constructor. Takes ownership of a memory block. - */ - shm_ptr( T *mem ) : ptr( mem ){} - - T *ptr; + T *ptr; //< the pointer to the shared object + size_t size; }; /** @@ -224,10 +217,9 @@ namespace XrdSys * @throws : an instance of shm_error in case of failure */ template - shm_ptr make_shm( const std::string &name, Args... args ) + inline shm_ptr make_shm( const std::string &name, Args&&... args ) { - void *mem = shm_ptr::create_shm( name, sizeof( T ) ); - return shm_ptr( mem, std::forward( args... ) ); + return shm_ptr( name, sizeof( T ), std::forward( args... ) ); } /** @@ -240,10 +232,9 @@ namespace XrdSys * @throws : an instance of shm_error in case of failure */ template - shm_ptr make_shm( const std::string &name ) + inline shm_ptr make_shm( const std::string &name ) { - void *mem = shm_ptr::create_shm( name, sizeof( T ) ); - return shm_ptr( mem ); + return shm_ptr( name, sizeof( T ) ); } /** @@ -256,12 +247,9 @@ namespace XrdSys * @throws : an instance of shm_error in case of failure */ template - shm_ptr get_shm( const std::string &name ) + inline shm_ptr get_shm( const std::string &name ) { - auto tpl = shm_ptr::init_shm( name ); - if( sizeof( T ) > std::get<1>( tpl ) ) - throw shm_error( EINVAL ); - return shm_ptr( reinterpret_cast( std::get<0>( tpl ) ) ); + return shm_ptr( name ); } /** From ad2960e6a5264ee2ccc22d04e621f23660f28101 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Feb 2023 16:48:58 +0100 Subject: [PATCH 183/773] [CMake] Add check for musl libc Out of principle musl libc provides no means for detecting it, leaving developers to figure it out for themselves. The choice here is to use the compiler to print the target triple, which contains the libc implementation as the last part. On Voidlinux it's x86_64-linux-musl, and on Alpine it's x86_64-alpine-linux-musl. This should be reliable enough to not wrongly detect other libc implementations as musl (and failure to detect will only affect platforms based on musl as well). See https://wiki.musl-libc.org/faq.html for more information. --- cmake/XRootDOSDefs.cmake | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmake/XRootDOSDefs.cmake b/cmake/XRootDOSDefs.cmake index 8b182e61ea1..9f7c9aa5940 100644 --- a/cmake/XRootDOSDefs.cmake +++ b/cmake/XRootDOSDefs.cmake @@ -70,6 +70,21 @@ if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) set( LINUX TRUE ) include( GNUInstallDirs ) set( EXTRA_LIBS rt ) + + # Check for musl libc with the compiler, since it provides way to detect it + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpmachine + OUTPUT_VARIABLE TARGET_TRIPLE ERROR_VARIABLE TARGET_ERROR) + + if (NOT TARGET_ERROR) + if ("${TARGET_TRIPLE}" MATCHES "musl") + message(STATUS "Detected musl libc") + add_definitions(-DMUSL=1) + endif() + else() + message(WARNING "Could not detect system information!") + endif() + + unset(TARGET_ERROR) endif() #------------------------------------------------------------------------------- From 075067321dc8fa13c6e2bf07e0d56b849e533efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20W=C3=B3jcik?= Date: Sat, 23 Jul 2022 21:37:47 +0200 Subject: [PATCH 184/773] Use standard poll.h, errno.h in place of sys/*.h --- src/Xrd/XrdPoll.hh | 2 +- src/XrdNet/XrdNetMsg.cc | 2 +- src/XrdOfs/XrdOfsHandle.cc | 2 +- src/XrdTls/XrdTlsTempCA.cc | 2 +- src/XrdVoms/XrdVomsMapfile.cc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Xrd/XrdPoll.hh b/src/Xrd/XrdPoll.hh index 3790b03b080..d9bda5d6277 100644 --- a/src/Xrd/XrdPoll.hh +++ b/src/Xrd/XrdPoll.hh @@ -29,7 +29,7 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ -#include +#include #include "XrdSys/XrdSysPthread.hh" #define XRD_NUMPOLLERS 3 diff --git a/src/XrdNet/XrdNetMsg.cc b/src/XrdNet/XrdNetMsg.cc index fa6c16f872a..f97e1af0996 100644 --- a/src/XrdNet/XrdNetMsg.cc +++ b/src/XrdNet/XrdNetMsg.cc @@ -29,7 +29,7 @@ /******************************************************************************/ #include -#include +#include #include "XrdNet/XrdNet.hh" #include "XrdNet/XrdNetMsg.hh" diff --git a/src/XrdOfs/XrdOfsHandle.cc b/src/XrdOfs/XrdOfsHandle.cc index 963f17a5a6a..26b40ce0887 100644 --- a/src/XrdOfs/XrdOfsHandle.cc +++ b/src/XrdOfs/XrdOfsHandle.cc @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include "XrdOfs/XrdOfsHandle.hh" diff --git a/src/XrdTls/XrdTlsTempCA.cc b/src/XrdTls/XrdTlsTempCA.cc index faf69785ffc..37431dee9f3 100644 --- a/src/XrdTls/XrdTlsTempCA.cc +++ b/src/XrdTls/XrdTlsTempCA.cc @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/XrdVoms/XrdVomsMapfile.cc b/src/XrdVoms/XrdVomsMapfile.cc index 8ffbe921875..fddbfdc06fb 100644 --- a/src/XrdVoms/XrdVomsMapfile.cc +++ b/src/XrdVoms/XrdVomsMapfile.cc @@ -40,7 +40,7 @@ #include #include #include -#include +#include bool XrdVomsMapfile::tried_configure = false; std::unique_ptr XrdVomsMapfile::mapper; From e281a27f314b257cc8300035dae125c0fc7719de Mon Sep 17 00:00:00 2001 From: Ben Jargowsky Date: Tue, 14 Feb 2023 17:02:09 +0100 Subject: [PATCH 185/773] Add (partial) support for musl libc Based on work from pull request #1632. Co-authored-by: Guilherme Amadio --- src/XrdPosix/XrdPosixLinkage.hh | 1 + src/XrdPosix/XrdPosixPreload.cc | 16 ++++++++++++++++ src/XrdSys/XrdSysPlatform.hh | 10 +--------- src/XrdSys/XrdSysPthread.hh | 4 ++-- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/XrdPosix/XrdPosixLinkage.hh b/src/XrdPosix/XrdPosixLinkage.hh index 9031532524c..b5cc6d4b469 100644 --- a/src/XrdPosix/XrdPosixLinkage.hh +++ b/src/XrdPosix/XrdPosixLinkage.hh @@ -40,6 +40,7 @@ #include #include "XrdPosix/XrdPosixOsDep.hh" +#include "XrdPosix/XrdPosixXrootd.hh" #include "XrdSys/XrdSysPlatform.hh" /******************************************************************************/ diff --git a/src/XrdPosix/XrdPosixPreload.cc b/src/XrdPosix/XrdPosixPreload.cc index 867b7347985..e01bc8a2301 100644 --- a/src/XrdPosix/XrdPosixPreload.cc +++ b/src/XrdPosix/XrdPosixPreload.cc @@ -41,6 +41,22 @@ /******************************************************************************/ #include "XrdPosix/XrdPosixExtern.hh" + +#ifdef MUSL +#undef creat64 +#undef fseeko64 +#undef ftello64 +#undef ftruncate64 +#undef lseek64 +#undef open64 +#undef pread64 +#undef pwrite64 +#undef readdir64 +#undef readdir64_r +#undef statfs64 +#undef statvfs64 +#undef truncate64 +#endif /******************************************************************************/ /* G l o b a l D e c l a r a t i o n s */ diff --git a/src/XrdSys/XrdSysPlatform.hh b/src/XrdSys/XrdSysPlatform.hh index a1ac544ad93..eaeaeabac58 100644 --- a/src/XrdSys/XrdSysPlatform.hh +++ b/src/XrdSys/XrdSysPlatform.hh @@ -241,16 +241,8 @@ extern "C" #if defined(_AIX) || \ (defined(XR__SUNGCC3) && !defined(__arch64__)) # define SOCKLEN_t size_t -#elif defined(XR__GLIBC) || \ - defined(__FreeBSD__) || \ - (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) || \ - (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 +# define SOCKLEN_t socklen_t #endif #ifdef _LP64 diff --git a/src/XrdSys/XrdSysPthread.hh b/src/XrdSys/XrdSysPthread.hh index 17f8fd0bec8..6d980246094 100644 --- a/src/XrdSys/XrdSysPthread.hh +++ b/src/XrdSys/XrdSysPthread.hh @@ -351,7 +351,7 @@ enum PrefType {prefWR=1}; XrdSysRWLock(PrefType ptype) { -#ifdef __linux__ +#if defined(__linux__) && (defined(__GLIBC__) || defined(__UCLIBC__)) pthread_rwlockattr_t attr; pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); @@ -367,7 +367,7 @@ enum PrefType {prefWR=1}; inline void ReInitialize(PrefType ptype) { pthread_rwlock_destroy(&lock); -#ifdef __linux__ +#if defined(__linux__) && (defined(__GLIBC__) || defined(__UCLIBC__)) pthread_rwlockattr_t attr; pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); From 0f5c2319a03ce28d2508d7ea7f08e5611fa5a829 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Feb 2023 13:12:19 +0100 Subject: [PATCH 186/773] Provide dummy implementation of innetgr() when compiling with musl --- src/XrdAcc/XrdAccGroups.cc | 8 ++++++++ src/XrdNet/XrdNetSecurity.cc | 13 ++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/XrdAcc/XrdAccGroups.cc b/src/XrdAcc/XrdAccGroups.cc index e663fdd35aa..94b8edbc75e 100644 --- a/src/XrdAcc/XrdAccGroups.cc +++ b/src/XrdAcc/XrdAccGroups.cc @@ -46,6 +46,14 @@ #include "XrdAcc/XrdAccGroups.hh" #include "XrdAcc/XrdAccPrivs.hh" +#ifdef MUSL +int innetgr(const char *netgroup, const char *host, const char *user, + const char *domain) +{ + return 0; +} +#endif + // Additionally, this routine does not support a user in more than // NGROUPS_MAX groups. This is a standard unix limit defined in limits.h. diff --git a/src/XrdNet/XrdNetSecurity.cc b/src/XrdNet/XrdNetSecurity.cc index 0f8451127ce..0faf563dd2e 100644 --- a/src/XrdNet/XrdNetSecurity.cc +++ b/src/XrdNet/XrdNetSecurity.cc @@ -40,11 +40,6 @@ #include #include #include -int innetgr(const char *netgroup, const char *host, const char *user, - const char *domain) -{ - return 0; -} #include "XrdSys/XrdWin32.hh" #endif @@ -53,6 +48,14 @@ int innetgr(const char *netgroup, const char *host, const char *user, #include "XrdNet/XrdNetUtils.hh" #include "XrdSys/XrdSysTrace.hh" +#if defined(MUSL) || defined(WIN32) +int innetgr(const char *netgroup, const char *host, const char *user, + const char *domain) +{ + return 0; +} +#endif + /******************************************************************************/ /* L o c a l C l a s s e s */ /******************************************************************************/ From aac3dd24c26854aeba75703cd204891bbc24b6da Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Feb 2023 17:12:11 +0100 Subject: [PATCH 187/773] [XrdOssCsi] Include byteswap.h everywhere except on macOS --- src/XrdOssCsi/XrdOssCsiTagstoreFile.hh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh b/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh index b1a94acd668..0202579ccb4 100644 --- a/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh +++ b/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh @@ -38,9 +38,7 @@ #include #include -#if defined(__GLIBC__) || defined(__BIONIC__) || defined(__CYGWIN__) -#include -#elif defined(__APPLE__) +#if defined(__APPLE__) // Make sure that byte swap functions are not already defined. #if !defined(bswap_16) // Mac OS X / Darwin features @@ -49,6 +47,8 @@ #define bswap_32(x) OSSwapInt32(x) #define bswap_64(x) OSSwapInt64(x) #endif +#else +#include #endif class XrdOssCsiTagstoreFile : public XrdOssCsiTagstore From 9ff4dc1a5d1f2777b67a1687e341b3c967a803cb Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Feb 2023 17:16:03 +0100 Subject: [PATCH 188/773] Include missing sys/types.h where needed to support musl --- src/Xrd/XrdConfig.hh | 2 ++ src/XrdPosix/XrdPosixFile.hh | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Xrd/XrdConfig.hh b/src/Xrd/XrdConfig.hh index a06ad462f4e..2d1403a5e10 100644 --- a/src/Xrd/XrdConfig.hh +++ b/src/Xrd/XrdConfig.hh @@ -34,6 +34,8 @@ #include "Xrd/XrdProtLoad.hh" #include "Xrd/XrdProtocol.hh" +#include + class XrdSysError; class XrdTcpMonInfo; class XrdNetSecurity; diff --git a/src/XrdPosix/XrdPosixFile.hh b/src/XrdPosix/XrdPosixFile.hh index e5cc6b1bda5..4ca9679f233 100644 --- a/src/XrdPosix/XrdPosixFile.hh +++ b/src/XrdPosix/XrdPosixFile.hh @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "XrdCl/XrdClFileSystem.hh" From 9f6f473bb1bcec49784b582af9c50678744fb05b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Feb 2023 17:16:24 +0100 Subject: [PATCH 189/773] Include missing sys/time.h where needed to support musl --- src/XrdCl/XrdClFileStateHandler.hh | 1 + src/XrdCl/XrdClMonitor.hh | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/XrdCl/XrdClFileStateHandler.hh b/src/XrdCl/XrdClFileStateHandler.hh index 197151321f8..f8353881950 100644 --- a/src/XrdCl/XrdClFileStateHandler.hh +++ b/src/XrdCl/XrdClFileStateHandler.hh @@ -39,6 +39,7 @@ #include #include +#include #include #include diff --git a/src/XrdCl/XrdClMonitor.hh b/src/XrdCl/XrdClMonitor.hh index 2fc7537776a..2b3a96c46fa 100644 --- a/src/XrdCl/XrdClMonitor.hh +++ b/src/XrdCl/XrdClMonitor.hh @@ -43,6 +43,8 @@ #include "XrdCl/XrdClFileSystem.hh" +#include + namespace XrdCl { class URL; From ecf528af5e152bad2c5f2c239dfe22bd1b7c75a1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Feb 2023 11:08:00 +0100 Subject: [PATCH 190/773] [XrdOuc] Adapt XrdOucBackTrace.cc to support musl The header execinfo.h is not available on musl, so the #ifdefs needed to be adjusted accordingly. This header is available on macOS, and with both glibc and uclibc on Linux, though. --- src/XrdOuc/XrdOucBackTrace.cc | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/XrdOuc/XrdOucBackTrace.cc b/src/XrdOuc/XrdOucBackTrace.cc index 813183c3228..c61a89b633d 100644 --- a/src/XrdOuc/XrdOucBackTrace.cc +++ b/src/XrdOuc/XrdOucBackTrace.cc @@ -34,7 +34,7 @@ #include #include -#ifdef __GNUC__ +#ifndef MUSL /* glibc, uclibc, and macOS all have execinfo.h */ #include #include #endif @@ -176,13 +176,11 @@ XrdInfo *CvtRsp(const char *name, int snum) /* D e m a n g l e */ /******************************************************************************/ +#ifndef MUSL namespace { int Demangle(char *cSym, char *buff, int blen) { -#ifndef __GNUC__ - return -1; -#else int status; char *plus = index(cSym, '+'); char *brak = (plus ? index(plus, '[') : 0); @@ -201,7 +199,6 @@ int Demangle(char *cSym, char *buff, int blen) status = snprintf(buff, blen, "%s %s+%s\n", brak, realname, plus+1); free(realname); return status; -#endif } } @@ -221,6 +218,7 @@ int DumpDepth() return (depth <= maxDepth ? depth : maxDepth); } } +#endif /******************************************************************************/ /* D u m p S t a c k */ @@ -230,8 +228,8 @@ namespace { void DumpStack(char *bP, int bL, TidType tid) { -#ifndef __GNUC__ - snprintf(bP, bL, "TBT " TidFmt " No stack information available, not gnuc.", tid); +#ifdef MUSL + snprintf(bP, bL, "TBT " TidFmt " No stack information available with musl libc.", tid); return; #else static int btDepth = DumpDepth(); // One time MT-safe call From 647e29428284cd5a4c313580023567a720bb8a19 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Feb 2023 17:35:56 +0100 Subject: [PATCH 191/773] [XrdPosix] Add utility functions fseterr/fseteof with musl support --- src/XrdPosix/XrdPosix.cc | 80 ++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/src/XrdPosix/XrdPosix.cc b/src/XrdPosix/XrdPosix.cc index 0f32dc2eed5..e4f020c09bf 100644 --- a/src/XrdPosix/XrdPosix.cc +++ b/src/XrdPosix/XrdPosix.cc @@ -53,6 +53,58 @@ extern XrdPosixLinkage Xunix; +/******************************************************************************/ +/* U t i l i t y F u n c t i o n s */ +/******************************************************************************/ + +#ifdef MUSL +#include +#endif + +static inline void fseterr(FILE *fp) +{ + /* Most systems provide FILE as a struct and the necessary bitmask in + , because they need it for implementing getc() and putc() as + fast macros. This function is based on gnulib's fseterr.c */ +#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__ + /* FreeBSD, NetBSD, OpenBSD, DragonFly, Mac OS X, Cygwin, Minix 3, Android */ + fp->_flags |= __SERR; +#elif defined _IOERR + /* AIX, HP-UX, IRIX, OSF/1, Solaris, OpenServer, UnixWare, mingw, MSVC, NonStop Kernel, OpenVMS */ + fp->_flag |= _IOERR; +#elif defined __UCLIBC__ /* uClibc */ + fp->__modeflags |= __FLAG_ERROR; +#elif defined MUSL /* musl libc */ + __fseterr(fp); +#else + #error "Unsupported platform! Please report it as a bug." +#endif +} + +static inline void fseteof(FILE *fp) +{ + /* Most systems provide FILE as a struct and the necessary bitmask in + , because they need it for implementing getc() and putc() as + fast macros. */ +#if defined _IO_EOF_SEEN || defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 + /* GNU libc, BeOS, Haiku, Linux libc5 */ + fp->_flags |= _IO_EOF_SEEN; +#elif defined __sferror || defined __APPLE__ || defined __DragonFly__ || defined __ANDROID__ + /* FreeBSD, NetBSD, OpenBSD, DragonFly, Mac OS X, Cygwin, Minix 3, Android */ + fp->_flags |= __SEOF; +#elif defined _IOEOF + /* AIX, HP-UX, IRIX, OSF/1, Solaris, OpenServer, UnixWare, mingw, MSVC, NonStop Kernel, OpenVMS */ + fp->_flag |= _IOEOF; +#elif defined __UCLIBC__ /* uClibc */ + fp->__modeflags |= __FLAG_EOF; +#else + (void) fseek(fp, 0L, SEEK_END); +#endif +} + /******************************************************************************/ /* X r d P o s i x _ A c c e s s */ /******************************************************************************/ @@ -308,19 +360,9 @@ size_t XrdPosix_Fread(void *ptr, size_t size, size_t nitems, FILE *stream) // Get the right return code. Note that we cannot emulate the flags in sunx86 // - if (bytes > 0 && size) rc = bytes/size; -#ifndef SUNX86 -#if defined(__linux__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) - else if (bytes < 0) stream->_flags |= _IO_ERR_SEEN; - else stream->_flags |= _IO_EOF_SEEN; -#elif defined(__APPLE__) || defined(__FreeBSD__) - else if (bytes < 0) stream->_flags |= __SEOF; - else stream->_flags |= __SERR; -#else - else if (bytes < 0) stream->_flag |= _IOERR; - else stream->_flag |= _IOEOF; -#endif -#endif + if (bytes > 0 && size) rc = bytes/size; + else if (bytes < 0) fseterr(stream); + else fseteof(stream); return rc; } @@ -477,18 +519,10 @@ size_t XrdPosix_Fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream bytes = Xroot.Write(fd, ptr, size*nitems); -// Get the right return code. Note that we cannot emulate the flags in sunx86 +// Get the right return code. // if (bytes > 0 && size) rc = bytes/size; -#ifndef SUNX86 -#if defined(__linux__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) - else stream->_flags |= _IO_ERR_SEEN; -#elif defined(__APPLE__) || defined(__FreeBSD__) - else stream->_flags |= __SERR; -#else - else stream->_flag |= _IOERR; -#endif -#endif + else fseterr(stream); return rc; } From cfd2b1da0cff73cc93f402fac48805e86751b4ed Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 15 Feb 2023 09:45:59 +0100 Subject: [PATCH 192/773] [CI] Add build on Alpine Linux This covers building with musl libc. --- .github/workflows/build.yml | 85 +++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 487aac2a16a..474b44a286e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -208,6 +208,91 @@ jobs: # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/FileSystemTest/' # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/LocalFileHandlerTest/' + 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 \ + 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@v2 + + - name: Build with cmake + run: | + cd .. + # need to fix ownership not to confuse git + chown -R -v "$( id -u; ):$( id -g; )" xrootd + cmake \ + -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 libXrdCl tests + run: | + cd ../build/tests + ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/UtilsTest/' + ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/SocketTest/' + ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/PollerTest/' + cmake-centos7: runs-on: ubuntu-latest From 0d3e34a66d1cac8b9eb68f0dd984af4ddd6ffe15 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 18 Feb 2023 13:18:07 -0600 Subject: [PATCH 193/773] Fix direct read for PFC When direct reads ("bypass mode") are in use, the code was failing to increment the number of bytes serviced in the read request. When there were no errors, that means the overall read operation returned 0 instead of the bytes read. This resulted in 100% failure rate for HTTP requests when direct read mode was activated. --- src/XrdPfc/XrdPfcFile.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/XrdPfc/XrdPfcFile.cc b/src/XrdPfc/XrdPfcFile.cc index d438c56b43e..c684ec27ce0 100644 --- a/src/XrdPfc/XrdPfcFile.cc +++ b/src/XrdPfc/XrdPfcFile.cc @@ -1171,8 +1171,10 @@ void File::ProcessDirectReadFinished(ReadRequest *rreq, int bytes_read, int erro if (error_cond) rreq->update_error_cond(error_cond); - else + else { rreq->m_stats.m_BytesBypassed += bytes_read; + rreq->m_bytes_read += bytes_read; + } rreq->m_direct_done = true; From 5ce32132b96a115d210225ca3f8cd9c251d42107 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Mon, 20 Feb 2023 09:57:38 +0100 Subject: [PATCH 194/773] [XrdSys] shm_ptr: allow access to the raw pointer. --- src/XrdSys/XrdSysShmem.hh | 112 +++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/src/XrdSys/XrdSysShmem.hh b/src/XrdSys/XrdSysShmem.hh index d5c681f457c..9841959e15d 100644 --- a/src/XrdSys/XrdSysShmem.hh +++ b/src/XrdSys/XrdSysShmem.hh @@ -69,56 +69,6 @@ namespace XrdSys public: - /** - * Constructor. Creates shared memory block (of size at least - * size) identified by name argument and maps it into user - * virtual address space and constructs within that memory - * object of type T using arguments args. - */ - template - shm_ptr( const std::string &name, size_t size, Args&&... args ) - { - void *mem = nullptr; - std::tie( mem, size ) = create_shm( name, size ); - ptr = new( mem ) T( std::forward( args... ) ); - } - - /** - * Constructor. Creates shared memory block (of size at least - * size) identified by name argument and maps it into user - * virtual address space and constructs within that memory - * object of type T using default constructor. - */ - shm_ptr( const std::string &name, size_t size ) - { - void *mem = nullptr; - std::tie( mem, size ) = create_shm( name, size ); - ptr = new( mem ) T(); - } - - /** - * Constructor for initializing from existing shared memory block. - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - */ - shm_ptr( const std::string &name ) - { - int fd = shm_open( name.c_str(), O_RDWR, 0600 ); - if( fd < 0 ) - throw shm_error( errno ); - struct stat statbuf; - if( fstat( fd, &statbuf ) < 0 ) - throw shm_error( errno ); - size = statbuf.st_size; - if( sizeof( T ) > size ) - throw shm_error( EINVAL ); - void *mem = map_shm( fd, size ); - close( fd ); - ptr = reinterpret_cast( mem ); - } - /// Move constructor shm_ptr( shm_ptr &&mv ) : ptr( mv.ptr ), size( mv.size ) { @@ -155,6 +105,18 @@ namespace XrdSys /// Dereferencing operator (const) const T& operator*() const { return *ptr; } + /// @return : the raw pointer to the shared memory block + inline void* get_raw() + { + return ptr; + } + + /// @return : the size of the shared memory block + inline size_t get_size() + { + return size; + } + private: /** @@ -201,6 +163,56 @@ namespace XrdSys return std::make_tuple( mem, size ); } + /** + * Constructor. Creates shared memory block (of size at least + * size) identified by name argument and maps it into user + * virtual address space and constructs within that memory + * object of type T using arguments args. + */ + template + shm_ptr( const std::string &name, size_t size, Args&&... args ) + { + void *mem = nullptr; + std::tie( mem, size ) = create_shm( name, size ); + ptr = new( mem ) T( std::forward( args... ) ); + } + + /** + * Constructor. Creates shared memory block (of size at least + * size) identified by name argument and maps it into user + * virtual address space and constructs within that memory + * object of type T using default constructor. + */ + shm_ptr( const std::string &name, size_t size ) + { + void *mem = nullptr; + std::tie( mem, size ) = create_shm( name, size ); + ptr = new( mem ) T(); + } + + /** + * Constructor for initializing from existing shared memory block. + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + */ + shm_ptr( const std::string &name ) + { + int fd = shm_open( name.c_str(), O_RDWR, 0600 ); + if( fd < 0 ) + throw shm_error( errno ); + struct stat statbuf; + if( fstat( fd, &statbuf ) < 0 ) + throw shm_error( errno ); + size = statbuf.st_size; + if( sizeof( T ) > size ) + throw shm_error( EINVAL ); + void *mem = map_shm( fd, size ); + close( fd ); + ptr = reinterpret_cast( mem ); + } + T *ptr; //< the pointer to the shared object size_t size; }; From 8afb89aaedc176ca25b5282a64f0516cc3f01a52 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 21 Feb 2023 10:13:44 +0100 Subject: [PATCH 195/773] [XrdSys] shm_ptr: add release method and destroy_shm friend function. --- src/XrdSys/XrdSysSharedMemory.hh | 183 ------------------------------- src/XrdSys/XrdSysShmem.hh | 52 +++++++-- 2 files changed, 44 insertions(+), 191 deletions(-) delete mode 100644 src/XrdSys/XrdSysSharedMemory.hh diff --git a/src/XrdSys/XrdSysSharedMemory.hh b/src/XrdSys/XrdSysSharedMemory.hh deleted file mode 100644 index 19a0119af55..00000000000 --- a/src/XrdSys/XrdSysSharedMemory.hh +++ /dev/null @@ -1,183 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2013 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_XRDSYS_XRDSYSSHAREDMEMORY_HH_ -#define SRC_XRDSYS_XRDSYSSHAREDMEMORY_HH_ - -#include -#include - -#include -#include -#include -#include - -namespace XrdSys -{ - /** - * A utility class for handling shared memory - */ - class SharedMemory - { - public: - - /** - * Constructor - * @param name : the name of the shared memory segment - * (shared memory object should be identified - * by a name of the form /somename) - */ - SharedMemory( const std::string &name ): - name( name ), size( 0 ), fd( -1 ), ptr( nullptr ){ } - - /** - * Destructor (unmaps the shared memory segment) - */ - ~SharedMemory() - { - if( ptr ) - munmap( ptr, size ); /* log errors */ - } - - /** - * Create a new shared memory segment - * @param size : size of the memory segment - * @return : 0 on success, -1 otherwise (check errno) - */ - int Create( size_t size ) - { - this->size = size; - fd = shm_open( name.c_str(), O_CREAT | O_RDWR, 0600 ); - if( fd < 0 ) - return -1; - if( ftruncate( fd, size ) < 0 ) - return -1; - Map(); - if( ptr == nullptr ) - return -1; - return 0; - } - - /** - * Open a shared memory segment - * - * @return : 0 on success, -1 otherwise (check errno) - */ - int Open() - { - fd = shm_open( name.c_str(), O_RDWR, 0600 ); - if( fd < 0 ) - return -1; - struct stat statbuf; - if( fstat( fd, &statbuf ) < 0 ) - return -1; - size = statbuf.st_size; - Map(); - if( ptr == nullptr ) - return -1; - return 0; - } - - /** - * @return : pointer to the shared memory segment and its size - */ - inline std::tuple Get() - { - return std::make_tuple( ptr, size ); - } - - /** - * Yields the ownership of the shared memory segment - * - * @return : pointer to the shared memory segment and its size - */ - inline std::tuple Move() - { - auto tpl = std::make_tuple( ptr, size ); - ptr = nullptr; - size = 0; - return tpl; - } - - /** - * Close the file descriptor corresponding to the - * shared memory segment (can be safely done after - * a pointer to the shared memory has been obtained) - * - * @return : 0 on success, -1 otherwise (check errno) - */ - inline int Close() - { - int rc = close( fd ); - if( rc == 0 ) - fd = -1; - return rc; - } - - /** - * Destroy the shared memory segment: - * - unmap the memory if necessary - * - close the file descriptor if necessary - * - unlink the shared memory block - * - * @return : 0 on success, -1 otherwise (check errno) - */ - int Destroy() - { - if( ptr ) - { - if( munmap( ptr, size ) < 0 ) - return -1; - ptr = nullptr; - } - if( fd != -1 ) - { - if( close( fd ) < 0 ) - return -1; - } - return shm_unlink( name.c_str() ); - } - - private: - - /** - * Map the shared memory block into processe's virtual - * address space. - */ - inline void Map() - { - ptr = mmap( nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); - if( ptr == MAP_FAILED ) - ptr = nullptr; - } - - std::string name; //> name of shared memory segment - size_t size; //> size of the shared memory segment - int fd; //> file descriptor pointing to the shared memory - void *ptr; //> pointer to the shared memory - }; -} - - -#endif /* SRC_XRDSYS_XRDSYSSHAREDMEMORY_HH_ */ diff --git a/src/XrdSys/XrdSysShmem.hh b/src/XrdSys/XrdSysShmem.hh index 9841959e15d..dcce7ef5ad1 100644 --- a/src/XrdSys/XrdSysShmem.hh +++ b/src/XrdSys/XrdSysShmem.hh @@ -66,11 +66,14 @@ namespace XrdSys friend shm_ptr make_shm( const std::string& ); template friend shm_ptr get_shm( const std::string& ); + template + friend void destroy_shm( shm_ptr ); public: /// Move constructor - shm_ptr( shm_ptr &&mv ) : ptr( mv.ptr ), size( mv.size ) + shm_ptr( shm_ptr &&mv ) : + name( std::move( mv.name ) ), ptr( mv.ptr ), size( mv.size ) { mv.ptr = nullptr; mv.size = 0; @@ -81,6 +84,7 @@ namespace XrdSys { ptr = mv.ptr; size = mv.size; + name = std::move( mv.name ); mv.ptr = nullptr; mv.size = 0; return *this; @@ -106,17 +110,26 @@ namespace XrdSys const T& operator*() const { return *ptr; } /// @return : the raw pointer to the shared memory block - inline void* get_raw() + inline T* get() { return ptr; } - /// @return : the size of the shared memory block + /// @return : the actual size of the shared memory block inline size_t get_size() { return size; } + /// release the ownership of the shared memory block + inline T* release() + { + T* ret = ptr; + ptr = nullptr; + size = 0; + return ret; + } + private: /** @@ -163,6 +176,11 @@ namespace XrdSys return std::make_tuple( mem, size ); } + /** + * Consumes a shm_ptr + */ + inline static void consume( shm_ptr ptr ) { } + /** * Constructor. Creates shared memory block (of size at least * size) identified by name argument and maps it into user @@ -170,7 +188,7 @@ namespace XrdSys * object of type T using arguments args. */ template - shm_ptr( const std::string &name, size_t size, Args&&... args ) + shm_ptr( const std::string &name, size_t size, Args&&... args ) : name( name ) { void *mem = nullptr; std::tie( mem, size ) = create_shm( name, size ); @@ -183,7 +201,7 @@ namespace XrdSys * virtual address space and constructs within that memory * object of type T using default constructor. */ - shm_ptr( const std::string &name, size_t size ) + shm_ptr( const std::string &name, size_t size ) : name( name ) { void *mem = nullptr; std::tie( mem, size ) = create_shm( name, size ); @@ -197,7 +215,7 @@ namespace XrdSys * memory object should be identified by a * name of the form /somename) */ - shm_ptr( const std::string &name ) + shm_ptr( const std::string &name ) : name( name ) { int fd = shm_open( name.c_str(), O_RDWR, 0600 ); if( fd < 0 ) @@ -213,8 +231,9 @@ namespace XrdSys ptr = reinterpret_cast( mem ); } - T *ptr; //< the pointer to the shared object - size_t size; + std::string name; //< name of the shared memory block + T *ptr; //< the pointer to the shared object + size_t size; //< actual size of the shared memory block }; /** @@ -274,6 +293,23 @@ namespace XrdSys if( rc < 0 ) throw shm_error( errno ); } + + /** + * Helper function for calling the object destructor and + * then destroying the shared memory block. + * @throws : an instance of shm_error in case of failure + */ + template + inline void destroy_shm( shm_ptr ptr ) + { + std::string name = ptr.name; + T* obj = ptr.get(); + obj->~T(); + shm_ptr::consume( std::move( ptr ) ); + int rc = shm_unlink( name.c_str() ); + if( rc < 0 ) + throw shm_error( errno ); + } } From 73b76cd7fc63d0fadc954f615b89425b73900d22 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 21 Feb 2023 09:57:54 +0100 Subject: [PATCH 196/773] [CI] Remove branch filters on push and pull_request triggers --- .github/workflows/build.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 474b44a286e..3a8dca0e634 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,14 +2,7 @@ name: build on: push: - branches: - - master - - stable-* - tags: - - v* pull_request: - branches: - - master schedule: - cron: '23 1 * * 0' release: From af69e89dd52aa2fc8fdcb9414f487a1b746a3afc Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Thu, 16 Feb 2023 08:56:46 -0600 Subject: [PATCH 197/773] Remove unnecessary GCC 4.1 checks. That's not supported anyway --- cmake/XRootDOSDefs.cmake | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cmake/XRootDOSDefs.cmake b/cmake/XRootDOSDefs.cmake index 9f7c9aa5940..3d4ae4abc73 100644 --- a/cmake/XRootDOSDefs.cmake +++ b/cmake/XRootDOSDefs.cmake @@ -50,14 +50,9 @@ if( CMAKE_COMPILER_IS_GNUCXX ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror" ) endif() set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter" ) - # gcc 4.1 is retarded + execute_process( COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION ) - if( (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1) - AND GCC_VERSION VERSION_LESS 4.2 ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing" ) - endif() - if( GCC_VERSION VERSION_GREATER 4.8.0 ) set( XrdClPipelines TRUE ) endif() From f702ed624f9d57fb155ebf9c28b11def766da1da Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 11 Nov 2022 15:35:15 +0100 Subject: [PATCH 198/773] [CMake] Add a CMake find module for isa-l --- cmake/Findisal.cmake | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 cmake/Findisal.cmake diff --git a/cmake/Findisal.cmake b/cmake/Findisal.cmake new file mode 100644 index 00000000000..06468f99373 --- /dev/null +++ b/cmake/Findisal.cmake @@ -0,0 +1,55 @@ +#.rst: +# Findisal +# --------- +# +# Find Intelligent Storage Acceleration Library. +# +# Result Variables +# ^^^^^^^^^^^^^^^^ +# +# This module defines the following variables: +# +# :: +# +# ISAL_FOUND - True if isa-l is found. +# ISAL_INCLUDE_DIRS - Where to find isa-l.h +# ISAL_LIBRARIES - Where to find libisal.so +# +# :: +# +# ISAL_VERSION - The version of ISAL found (x.y.z) +# ISAL_VERSION_MAJOR - The major version of isa-l +# ISAL_VERSION_MINOR - The minor version of isa-l +# ISAL_VERSION_PATCH - The patch version of isa-l + +foreach(var ISAL_FOUND ISAL_INCLUDE_DIR ISAL_ISAL_LIBRARY ISAL_LIBRARIES) + unset(${var} CACHE) +endforeach() + +find_path(ISAL_INCLUDE_DIR NAME isa-l.h PATH_SUFFIXES include) + +if(NOT ISAL_LIBRARY) + find_library(ISAL_LIBRARY NAMES isal PATH_SUFFIXES lib) +endif() + +mark_as_advanced(ISAL_INCLUDE_DIR) + +if(ISAL_INCLUDE_DIR AND EXISTS "${ISAL_INCLUDE_DIR}/isa-l.h") + file(STRINGS "${ISAL_INCLUDE_DIR}/isa-l.h" ISAL_H REGEX "^#define ISAL_[A-Z_]+[ ]+[0-9]+.*$") + string(REGEX REPLACE ".+ISAL_MAJOR_VERSION[ ]+([0-9]+).*$" "\\1" ISAL_VERSION_MAJOR "${ISAL_H}") + string(REGEX REPLACE ".+ISAL_MINOR_VERSION[ ]+([0-9]+).*$" "\\1" ISAL_VERSION_MINOR "${ISAL_H}") + string(REGEX REPLACE ".+ISAL_PATCH_VERSION[ ]+([0-9]+).*$" "\\1" ISAL_VERSION_PATCH "${ISAL_H}") + set(ISAL_VERSION "${ISAL_VERSION_MAJOR}.${ISAL_VERSION_MINOR}.${ISAL_VERSION_PATCH}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(isal + REQUIRED_VARS ISAL_LIBRARY ISAL_INCLUDE_DIR VERSION_VAR ISAL_VERSION) + +if(ISAL_FOUND) + set(ISAL_INCLUDE_DIRS "${ISAL_INCLUDE_DIR}") + + if(NOT ISAL_LIBRARIES) + set(ISAL_LIBRARIES ${ISAL_LIBRARY}) + endif() +endif() From fb01136cc18af34447d9438827cc736f720e4c24 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 11 Nov 2022 17:33:00 +0100 Subject: [PATCH 199/773] [CMake] Adapt build system to be able to use pre-installed isa-l --- cmake/XRootDDefaults.cmake | 1 + src/XrdCl/CMakeLists.txt | 11 +--- src/XrdEc/CMakeLists.txt | 8 --- src/XrdEc/XrdEcObjCfg.hh | 2 +- src/XrdEc/XrdEcRedundancyProvider.cc | 2 +- src/XrdIsal.cmake | 86 ++++++++++++---------------- 6 files changed, 44 insertions(+), 66 deletions(-) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index 5c25975bb29..1bef0cce3be 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -33,4 +33,5 @@ option( ENABLE_XRDCLHTTP "Enable xrdcl-http plugin." cmake_dependent_option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE "NOT XRDCL_ONLY" FALSE ) cmake_dependent_option( ENABLE_MACAROONS "Enable Macaroons plugin." TRUE "NOT XRDCL_ONLY" FALSE ) option( FORCE_ENABLED "Fail build if enabled components cannot be built." FALSE ) +cmake_dependent_option( USE_SYSTEM_ISAL "Use isa-l installed in the system" FALSE "ENABLE_XRDEC" FALSE ) define_default( XRD_PYTHON_REQ_VERSION 3 ) diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index fb43d1f1b4c..32d3ccb4e14 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -28,8 +28,6 @@ endif() # XrdEc sources #------------------------------------------------------------------------------- if( BUILD_XRDEC ) - link_directories( ${ISAL_LIBDIR} ) - include_directories( ${ISAL_INCDIR} ) set( XrdEcSources ${CMAKE_SOURCE_DIR}/src/XrdEc/XrdEcRedundancyProvider.cc ${CMAKE_SOURCE_DIR}/src/XrdEc/XrdEcUtilities.cc @@ -37,7 +35,6 @@ if( BUILD_XRDEC ) ${CMAKE_SOURCE_DIR}/src/XrdEc/XrdEcReader.cc XrdClEcHandler.cc ) - set( ISAL_LIB isal ) add_compile_definitions( WITH_XRDEC ) endif() @@ -119,19 +116,17 @@ target_link_libraries( ${ZLIB_LIBRARIES} ${EXTRA_LIBS} ${CMAKE_DL_LIBS} - ${OPENSSL_LIBRARIES} - ${ISAL_LIB}) + ${OPENSSL_LIBRARIES}) set_target_properties( XrdCl PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" VERSION ${XRD_CL_VERSION} SOVERSION ${XRD_CL_SOVERSION} ) if( BUILD_XRDEC ) - add_dependencies( XrdCl isa-l ) + target_include_directories(XrdCl PUBLIC ${ISAL_INCLUDE_DIRS}) + target_link_libraries(XrdCl ${ISAL_LIBRARIES}) endif() #------------------------------------------------------------------------------- diff --git a/src/XrdEc/CMakeLists.txt b/src/XrdEc/CMakeLists.txt index 43aa3ff5eaf..53754340c2c 100644 --- a/src/XrdEc/CMakeLists.txt +++ b/src/XrdEc/CMakeLists.txt @@ -1,9 +1,6 @@ include( XRootDCommon ) include( ExternalProject ) -link_directories( ${ISAL_LIBDIR} ) -include_directories( ${ISAL_INCDIR} ) - #------------------------------------------------------------------------------- # The XrdEc shared library #------------------------------------------------------------------------------- @@ -27,19 +24,14 @@ add_library( target_link_libraries( XrdEc XrdCl - isal ) set_target_properties( XrdEc PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" VERSION ${XRD_EC_VERSION} SOVERSION ${XRD_EC_SOVERSION} ) -add_dependencies( XrdEc isa-l ) - #------------------------------------------------------------------------------ # Install XrdEc library #------------------------------------------------------------------------------ diff --git a/src/XrdEc/XrdEcObjCfg.hh b/src/XrdEc/XrdEcObjCfg.hh index 3978b1c7738..24fb99429e1 100644 --- a/src/XrdEc/XrdEcObjCfg.hh +++ b/src/XrdEc/XrdEcObjCfg.hh @@ -10,7 +10,7 @@ #include "XrdOuc/XrdOucCRC32C.hh" -#include "isa-l/crc.h" +#include #include #include diff --git a/src/XrdEc/XrdEcRedundancyProvider.cc b/src/XrdEc/XrdEcRedundancyProvider.cc index 47f48a431c3..c41232aee28 100644 --- a/src/XrdEc/XrdEcRedundancyProvider.cc +++ b/src/XrdEc/XrdEcRedundancyProvider.cc @@ -15,7 +15,7 @@ #include "XrdEc/XrdEcRedundancyProvider.hh" -#include "isa-l/isa-l.h" +#include #include #include #include diff --git a/src/XrdIsal.cmake b/src/XrdIsal.cmake index fe819f49535..f110df69dc3 100644 --- a/src/XrdIsal.cmake +++ b/src/XrdIsal.cmake @@ -1,57 +1,47 @@ -include( XRootDCommon ) -include( ExternalProject ) +if(USE_SYSTEM_ISAL) + find_package(isal REQUIRED) +endif() + +if(ISAL_FOUND) + return() +endif() #------------------------------------------------------------------------------- # Build isa-l #------------------------------------------------------------------------------- -set(MAKEOPTIONS "") -if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i386" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i686") - set(MAKEOPTIONS "arch=32") -endif() - -#EXECUTE_PROCESS( -# COMMAND git ls-remote --tags https://github.com/01org/isa-l -# COMMAND awk "{print $2}" -# COMMAND grep -v {} -# COMMAND awk -F "/" "{print $3}" -# COMMAND tail -1 -# OUTPUT_VARIABLE ISAL_VERSION -#) - -set( ISAL_VERSION v2.30.0 ) -MESSAGE( STATUS "Building ISAL: ${ISAL_VERSION}" ) - -set( ISAL_BUILDDIR "${CMAKE_BINARY_DIR}/isal/build" CACHE INTERNAL "" ) -set( ISAL_INCDIR "${CMAKE_BINARY_DIR}/isal/include" CACHE INTERNAL "" ) -set( ISAL_LIBDIR "${CMAKE_BINARY_DIR}/isal/lib" CACHE INTERNAL "" ) - -set( ISAL_HEADERS - ${ISAL_BUILDDIR}/include/crc64.h - ${ISAL_BUILDDIR}/include/crc.h - ${ISAL_BUILDDIR}/include/erasure_code.h - ${ISAL_BUILDDIR}/include/gf_vect_mul.h - ${ISAL_BUILDDIR}/include/igzip_lib.h - ${ISAL_BUILDDIR}/include/mem_routines.h - ${ISAL_BUILDDIR}/include/multibinary.asm - ${ISAL_BUILDDIR}/include/raid.h - ${ISAL_BUILDDIR}/include/reg_sizes.asm - ${ISAL_BUILDDIR}/include/test.h - ${ISAL_BUILDDIR}/include/types.h +include(ExternalProject) +include(FindPackageHandleStandardArgs) + +set(ISAL_VERSION v2.30.0) +message(STATUS "Building ISAL: ${ISAL_VERSION}") + +set(ISAL_ROOT "${CMAKE_BINARY_DIR}/isa-l") +set(ISAL_LIBRARY "${ISAL_ROOT}/.libs/libisal.a") +set(ISAL_INCLUDE_DIRS "${ISAL_ROOT}") + +ExternalProject_add(isa-l + URL https://github.com/intel/isa-l/archive/refs/tags/${ISAL_VERSION}.tar.gz + URL_HASH SHA256=bcf592c04fdfa19e723d2adf53d3e0f4efd5b956bb618fed54a1108d76a6eb56 + SOURCE_DIR ${CMAKE_BINARY_DIR}/isa-l + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND ./autogen.sh COMMAND ./configure --with-pic + 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} ) -ExternalProject_add( - isa-l - SOURCE_DIR ${ISAL_BUILDDIR} - BUILD_IN_SOURCE 1 - GIT_REPOSITORY https://github.com/01org/isa-l.git - GIT_TAG ${ISAL_VERSION} - CONFIGURE_COMMAND ./autogen.sh COMMAND ./configure --with-pic - BUILD_COMMAND make ${MAKEOPTIONS} - INSTALL_COMMAND mkdir -p ${ISAL_INCDIR}/isa-l - COMMAND mkdir -p ${ISAL_LIBDIR} - COMMAND cp ${ISAL_HEADERS} ${ISAL_INCDIR}/isa-l - COMMAND cp ${ISAL_BUILDDIR}/isa-l.h ${ISAL_INCDIR}/isa-l - COMMAND cp ${ISAL_BUILDDIR}/.libs/libisal.a ${ISAL_LIBDIR}/ +add_library(isal STATIC IMPORTED) + +set(ISAL_LIBRARIES isal) +add_dependencies(isal isa-l) + +set_target_properties(isal + PROPERTIES + IMPORTED_LOCATION "${ISAL_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "$" ) +# Emulate what happens when find_package(isal) succeeds +find_package_handle_standard_args(isal + REQUIRED_VARS ISAL_INCLUDE_DIRS ISAL_LIBRARIES VERSION_VAR ISAL_VERSION) From 02813485c490194f66cd9455ae8b0906477c7abb Mon Sep 17 00:00:00 2001 From: Andreas Joachim Peters Date: Mon, 20 Feb 2023 11:41:23 +0100 Subject: [PATCH 200/773] [XrdSecztn] Allow option '-tokenlib none' to disable token validation --- src/XrdSecztn/XrdSecProtocolztn.cc | 38 +++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/XrdSecztn/XrdSecProtocolztn.cc b/src/XrdSecztn/XrdSecProtocolztn.cc index 52d2b767d67..a4d8f6c53c0 100644 --- a/src/XrdSecztn/XrdSecProtocolztn.cc +++ b/src/XrdSecztn/XrdSecProtocolztn.cc @@ -107,6 +107,7 @@ inline uint64_t monotonic_time() { /******************************************************************************/ int expiry = 1; +bool tokenlib = true; } /******************************************************************************/ @@ -631,8 +632,9 @@ int XrdSecProtocolztn::Authenticate(XrdSecCredentials *cred, // std::string msgRC; long long eTime; + bool validated = false; if (Entity.name) {free(Entity.name); Entity.name = 0;} - if (sthP->Validate(tResp->tkn, msgRC, (expiry ? &eTime : 0), &Entity)) + if (tokenlib && sthP->Validate(tResp->tkn, msgRC, (expiry ? &eTime : 0), &Entity)) {if (expiry) {if (eTime < 0 && expiry > 0) {Fatal(erp, "'ztn' token expiry missing", EINVAL, false); @@ -642,7 +644,11 @@ int XrdSecProtocolztn::Authenticate(XrdSecCredentials *cred, {Fatal(erp, "'ztn' token expired", EINVAL, false); return -1; } + } + validated = true; } + if (!tokenlib || validated) + { Entity.credslen = strlen(tResp->tkn); if (Entity.creds) free(Entity.creds); Entity.creds = (char *)malloc(Entity.credslen+1); @@ -744,7 +750,12 @@ char *XrdSecProtocolztnInit(const char mode, {Fatal(erp, "-acclib plugin path missing", EINVAL); return 0; } - accPlugin = val; + if (strcmp(val,"none")) + {accPlugin = val; + } + else + {tokenlib = false; + } } else {XrdOucString eTxt("Invalid parameter - "); eTxt += val; @@ -753,11 +764,12 @@ char *XrdSecProtocolztnInit(const char mode, } } -// We rely on the token authorization plugin to validate tokens. Load it to +// We rely on the token authorization plugin to validate tokens unless +// it is disabled using '-tokenlib none'. If active load it to // get the validation object pointer. This will be filled in later but we // want to know that it's actually present. // - if (!getLinkage(erp, accPlugin.c_str())) return 0; + if (tokenlib && !getLinkage(erp, accPlugin.c_str())) return 0; // Assemble the parameter line and return it // @@ -799,16 +811,20 @@ XrdSecProtocol *XrdSecProtocolztnObject(const char mode, return 0; } + XrdSciTokensHelper *sthP= nullptr; + if (tokenlib) + { // In server mode we need to make sure the token plugin was actually // loaded and initialized as we need a pointer to the helper. // - XrdSciTokensHelper *sthP= *sth_Linkage; - if (!sthP) - {char msg[1024]; - snprintf(msg,sizeof(msg),"ztn required plugin (%s) has not been loaded!", - sth_piName); - Fatal(erp, msg, EIDRM,false); - return 0; + sthP= *sth_Linkage; + if (!sthP) + {char msg[1024]; + snprintf(msg,sizeof(msg),"ztn required plugin (%s) has not been loaded!", + sth_piName); + Fatal(erp, msg, EIDRM,false); + return 0; + } } // Get an authentication object and return it From 75c8e03d2b3a59fc1c45d7015f998b58114e2114 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 18 Feb 2023 13:15:49 -0600 Subject: [PATCH 201/773] 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. --- src/XrdHttp/XrdHttpReq.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index ccd17a22781..92b89fa76b2 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -1016,6 +1016,9 @@ void XrdHttpReq::mapXrdErrorToHttpStatus() { // Do error mapping if (xrdresp == kXR_error) { switch (xrderrcode) { + case kXR_AuthFailed: + httpStatusCode = 401; httpStatusText = "Unauthorized"; + break; case kXR_NotAuthorized: httpStatusCode = 403; httpStatusText = "Operation not permitted"; break; From 8f5b34532bf50e95680ef57c320f5c35c133fdf5 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 22 Feb 2023 13:10:46 +0100 Subject: [PATCH 202/773] [XrdSys] Replace shm_ptr with shm_buffer. --- src/XrdSys/XrdSysShmem.hh | 690 ++++++++++++++++++++++++++------------ 1 file changed, 469 insertions(+), 221 deletions(-) diff --git a/src/XrdSys/XrdSysShmem.hh b/src/XrdSys/XrdSysShmem.hh index dcce7ef5ad1..e925b84606f 100644 --- a/src/XrdSys/XrdSysShmem.hh +++ b/src/XrdSys/XrdSysShmem.hh @@ -35,6 +35,7 @@ namespace XrdSys { + /** * Shared memory exception type. */ @@ -48,90 +49,58 @@ namespace XrdSys }; /** - * Smart pointer for encapsulating shared memory segments. - * The destructor will automatically unmap the memory from - * user's virtual address space. - * - * It is user responsibility to finalize the T object - * (the shm_ptr does not ensure calling a destructor - * for the object). + * Factory methods for obtaining shared memory blocks */ - template - class shm_ptr + struct shm { - /// Friend factory methods - template - friend shm_ptr make_shm( const std::string&, Args... ); - template - friend shm_ptr make_shm( const std::string& ); - template - friend shm_ptr get_shm( const std::string& ); - template - friend void destroy_shm( shm_ptr ); - - public: - - /// Move constructor - shm_ptr( shm_ptr &&mv ) : - name( std::move( mv.name ) ), ptr( mv.ptr ), size( mv.size ) - { - mv.ptr = nullptr; - mv.size = 0; - } - - /// Move assignment operator - shm_ptr& operator=( shm_ptr &&mv ) - { - ptr = mv.ptr; - size = mv.size; - name = std::move( mv.name ); - mv.ptr = nullptr; - mv.size = 0; - return *this; - } - - /// Destructor - ~shm_ptr() - { - if( ptr ) - munmap( ptr, size ); - } - - /// Member access operator - T* operator->() { return ptr; } - - /// Member access operator (const) - const T* operator->() const { return ptr; } - - /// Dereferencing operator - T& operator*() { return *ptr; } - - /// Dereferencing operator (const) - const T& operator*() const { return *ptr; } - - /// @return : the raw pointer to the shared memory block - inline T* get() - { - return ptr; - } - - /// @return : the actual size of the shared memory block - inline size_t get_size() - { - return size; - } - - /// release the ownership of the shared memory block - inline T* release() - { - T* ret = ptr; - ptr = nullptr; - size = 0; - return ret; - } + /** + * Helper function for creating shared memory block + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @param size : size of the shared memory segment + * @return : pointer to the shared memory and its size + */ + inline static std::tuple create( const std::string &name, size_t size ) + { + int fd = shm_open( name.c_str(), O_CREAT | O_RDWR, 0600 ); + if( fd < 0 ) + throw shm_error( errno ); + if( ftruncate( fd, size ) < 0 ) + throw shm_error( errno ); + struct stat statbuf; + if( fstat( fd, &statbuf ) < 0 ) + throw shm_error( errno ); + size = statbuf.st_size; + void *mem = map_shm( fd, size ); + close( fd ); + return std::make_tuple( mem, size ); + } + + /** + * Helper function for getting shared memory block + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @return : pointer to the shared memory and its size + */ + inline static std::tuple get( const std::string &name ) + { + int fd = shm_open( name.c_str(), O_RDWR, 0600 ); + if( fd < 0 ) + throw shm_error( errno ); + struct stat statbuf; + if( fstat( fd, &statbuf ) < 0 ) + throw shm_error( errno ); + size_t size = statbuf.st_size; + void *mem = map_shm( fd, size ); + close( fd ); + return std::make_tuple( mem, size ); + } private: - /** * Helper function for mapping the shared memory block * into user virtual address space @@ -150,63 +119,60 @@ namespace XrdSys } return mem; } + }; - /** - * Helper function for creating shared memory block - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - * @param size : size of the shared memory segment - * @return : pointer to the shared memory - */ - inline static std::tuple create_shm( const std::string &name, size_t size ) - { - int fd = shm_open( name.c_str(), O_CREAT | O_RDWR, 0600 ); - if( fd < 0 ) - throw shm_error( errno ); - if( ftruncate( fd, size ) < 0 ) - throw shm_error( errno ); - struct stat statbuf; - if( fstat( fd, &statbuf ) < 0 ) - throw shm_error( errno ); - size = statbuf.st_size; - void *mem = map_shm( fd, size ); - close( fd ); - return std::make_tuple( mem, size ); - } - - /** - * Consumes a shm_ptr - */ - inline static void consume( shm_ptr ptr ) { } + /** + * A shared memory buffer of objects of type T + */ + template + class shm_buffer + { + public: /** - * Constructor. Creates shared memory block (of size at least - * size) identified by name argument and maps it into user - * virtual address space and constructs within that memory - * object of type T using arguments args. + * Constructor. Creates shared memory block (big enough to + * accommodate `count` objects of type T) identified by + * name argument and maps it into user virtual address space + * and constructs within that memory `count` objects of type + * T using default constructor. + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @param count : number of object in the buffer */ - template - shm_ptr( const std::string &name, size_t size, Args&&... args ) : name( name ) - { - void *mem = nullptr; - std::tie( mem, size ) = create_shm( name, size ); - ptr = new( mem ) T( std::forward( args... ) ); - } + shm_buffer( const std::string &name, size_t count ) : + name( name ), count( count ) + { + void *ptr; + std::tie( ptr, size ) = shm::create( name, count * sizeof(T) ); + buff = reinterpret_cast( ptr ); + for( size_t i = 0; i < count; ++i ) + new( buff + i ) T(); + } /** - * Constructor. Creates shared memory block (of size at least - * size) identified by name argument and maps it into user - * virtual address space and constructs within that memory - * object of type T using default constructor. + * Constructor. Creates shared memory block (big enough to + * accommodate `count` objects of type T) identified by + * name argument and maps it into user virtual address space + * and constructs within that memory `count` objects of type + * T using arguments args + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @param count : number of object in the buffer */ - shm_ptr( const std::string &name, size_t size ) : name( name ) - { - void *mem = nullptr; - std::tie( mem, size ) = create_shm( name, size ); - ptr = new( mem ) T(); - } + template + shm_buffer( const std::string &name, size_t count, Args&&... args ) : + name( name ), count( count ) + { + void *ptr; + std::tie( ptr, size ) = shm::create( name, count * sizeof(T) ); + buff = reinterpret_cast( ptr ); + for( size_t i = 0; i < count; ++i ) + new( buff + i ) T( args... ); + } /** * Constructor for initializing from existing shared memory block. @@ -215,101 +181,383 @@ namespace XrdSys * memory object should be identified by a * name of the form /somename) */ - shm_ptr( const std::string &name ) : name( name ) - { - int fd = shm_open( name.c_str(), O_RDWR, 0600 ); - if( fd < 0 ) - throw shm_error( errno ); - struct stat statbuf; - if( fstat( fd, &statbuf ) < 0 ) + shm_buffer( const std::string &name ) : name( name ) + { + void *ptr; + std::tie( ptr, size ) = shm::get( name ); + buff = reinterpret_cast( ptr ); + } + + /** + * Move constructor + */ + shm_buffer( shm_buffer && mv ) : + name( std::move( mv.name ) ), + count( mv.count ), + size( mv.size ), + buff( mv.buff ) + { + mv.count = 0; + mv.size = 0; + mv.buff = nullptr; + } + + /** + * Destructor + */ + ~shm_buffer() + { + reset(); + } + + /** + * Move assigment operator + */ + shm_buffer& operator=( shm_buffer && mv ) + { + name = std::move( mv.name ); + count = mv.count; + size = mv.size; + buff = mv.buff; + mv.count = 0; + mv.size = 0; + mv.buff = nullptr; + } + + /** + * Subscript operator + */ + T& operator[]( size_t idx ) + { + return buff[idx]; + } + + /** + * Subscript operator (const) + */ + const T& operator[]( size_t idx ) const + { + return buff[idx]; + } + + /** + * Unmap the block from the user virtual memory space + * and reset the object + */ + inline void reset() + { + if( buff ) + munmap( buff, size ); + name.clear(); + count = 0; + size = 0; + buff = nullptr; + } + + /** + * Release the ownership of the shared memory block + * + * @return : the shared memory block + */ + inline T* release() + { + T *ptr = buff; + name.clear(); + count = 0; + size = 0; + buff = nullptr; + return ptr; + } + + /** + * Finalize the object and destroy the associated + * shared memory block. + * + * @param buffer : the object to be destroyed + */ + inline static void destroy( shm_buffer && buffer ) + { + std::string name = buffer.name; + for( size_t i = 0; i < buffer.count; ++i ) + buffer.buff[i].~T(); + buffer.reset(); + int rc = shm_unlink( name.c_str() ); + if( rc < 0 ) throw shm_error( errno ); - size = statbuf.st_size; - if( sizeof( T ) > size ) - throw shm_error( EINVAL ); - void *mem = map_shm( fd, size ); - close( fd ); - ptr = reinterpret_cast( mem ); - } + } - std::string name; //< name of the shared memory block - T *ptr; //< the pointer to the shared object - size_t size; //< actual size of the shared memory block + private: + std::string name; //< shared memory block name + size_t count; //< object count in the buffer + size_t size; //< the size of the shared memory block + T* buff; //< pointer to the shared memory block }; - /** - * Factory for creating a shared memory block. - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - * @param args : argument to be passed to constructor of - * type T - * @return : pointer to the shared memory - * @throws : an instance of shm_error in case of failure - */ - template - inline shm_ptr make_shm( const std::string &name, Args&&... args ) - { - return shm_ptr( name, sizeof( T ), std::forward( args... ) ); - } - - /** - * Factory for creating a shared memory block. - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - * @return : pointer to the shared memory - * @throws : an instance of shm_error in case of failure - */ - template - inline shm_ptr make_shm( const std::string &name ) - { - return shm_ptr( name, sizeof( T ) ); - } - - /** - * Factory for getting an existing shared memory block. - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - * @return : pointer to the shared memory - * @throws : an instance of shm_error in case of failure - */ - template - inline shm_ptr get_shm( const std::string &name ) - { - return shm_ptr( name ); - } - - /** - * Helper function for deleting an existing shared memory block. - * @throws : an instance of shm_error in case of failure - */ - inline void rm_shm( const std::string &name ) - { - int rc = shm_unlink( name.c_str() ); - if( rc < 0 ) - throw shm_error( errno ); - } - - /** - * Helper function for calling the object destructor and - * then destroying the shared memory block. - * @throws : an instance of shm_error in case of failure - */ - template - inline void destroy_shm( shm_ptr ptr ) - { - std::string name = ptr.name; - T* obj = ptr.get(); - obj->~T(); - shm_ptr::consume( std::move( ptr ) ); - int rc = shm_unlink( name.c_str() ); - if( rc < 0 ) - throw shm_error( errno ); - } +// +// +// /** +// * Smart pointer for encapsulating shared memory segments. +// * The destructor will automatically unmap the memory from +// * user's virtual address space. +// * +// * It is user responsibility to finalize the T object +// * (the shm_ptr does not ensure calling a destructor +// * for the object). +// */ +// template +// class shm_ptr +// { +// /// Friend factory methods +// template +// friend shm_ptr make_shm( const std::string&, Args... ); +// template +// friend shm_ptr make_shm( const std::string& ); +// template +// friend shm_ptr get_shm( const std::string& ); +// template +// friend void destroy_shm( shm_ptr ); +// +// public: +// +// /// Move constructor +// shm_ptr( shm_ptr &&mv ) : +// name( std::move( mv.name ) ), ptr( mv.ptr ), size( mv.size ) +// { +// mv.ptr = nullptr; +// mv.size = 0; +// } +// +// /// Move assignment operator +// shm_ptr& operator=( shm_ptr &&mv ) +// { +// ptr = mv.ptr; +// size = mv.size; +// name = std::move( mv.name ); +// mv.ptr = nullptr; +// mv.size = 0; +// return *this; +// } +// +// /// Destructor +// ~shm_ptr() +// { +// if( ptr ) +// munmap( ptr, size ); +// } +// +// /// Member access operator +// T* operator->() { return ptr; } +// +// /// Member access operator (const) +// const T* operator->() const { return ptr; } +// +// /// Dereferencing operator +// T& operator*() { return *ptr; } +// +// /// Dereferencing operator (const) +// const T& operator*() const { return *ptr; } +// +// /// @return : the raw pointer to the shared memory block +// inline T* get() +// { +// return ptr; +// } +// +// /// @return : the actual size of the shared memory block +// inline size_t get_size() +// { +// return size; +// } +// +// /// release the ownership of the shared memory block +// inline T* release() +// { +// T* ret = ptr; +// ptr = nullptr; +// size = 0; +// return ret; +// } +// +// private: +// +// /** +// * Helper function for mapping the shared memory block +// * into user virtual address space +// * +// * @param fd : handle to the shared memory segment +// * @param size : size of the shared memory segment +// * @return : pointer to the shared memory +// */ +// inline static void* map_shm( int fd, size_t size ) +// { +// void *mem = mmap( nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); +// if( mem == MAP_FAILED ) +// { +// mem = nullptr; +// throw shm_error( errno ); +// } +// return mem; +// } +// +// /** +// * Helper function for creating shared memory block +// * +// * @param name : name of the shared memory block (shared +// * memory object should be identified by a +// * name of the form /somename) +// * @param size : size of the shared memory segment +// * @return : pointer to the shared memory +// */ +// inline static std::tuple create_shm( const std::string &name, size_t size ) +// { +// int fd = shm_open( name.c_str(), O_CREAT | O_RDWR, 0600 ); +// if( fd < 0 ) +// throw shm_error( errno ); +// if( ftruncate( fd, size ) < 0 ) +// throw shm_error( errno ); +// struct stat statbuf; +// if( fstat( fd, &statbuf ) < 0 ) +// throw shm_error( errno ); +// size = statbuf.st_size; +// void *mem = map_shm( fd, size ); +// close( fd ); +// return std::make_tuple( mem, size ); +// } +// +// /** +// * Consumes a shm_ptr +// */ +// inline static void consume( shm_ptr ptr ) { } +// +// /** +// * Constructor. Creates shared memory block (of size at least +// * size) identified by name argument and maps it into user +// * virtual address space and constructs within that memory +// * object of type T using arguments args. +// */ +// template +// shm_ptr( const std::string &name, size_t size, Args&&... args ) : name( name ) +// { +// void *mem = nullptr; +// std::tie( mem, size ) = create_shm( name, size ); +// ptr = new( mem ) T( std::forward( args... ) ); +// } +// +// /** +// * Constructor. Creates shared memory block (of size at least +// * size) identified by name argument and maps it into user +// * virtual address space and constructs within that memory +// * object of type T using default constructor. +// */ +// shm_ptr( const std::string &name, size_t size ) : name( name ) +// { +// void *mem = nullptr; +// std::tie( mem, size ) = create_shm( name, size ); +// ptr = new( mem ) T(); +// } +// +// /** +// * Constructor for initializing from existing shared memory block. +// * +// * @param name : name of the shared memory block (shared +// * memory object should be identified by a +// * name of the form /somename) +// */ +// shm_ptr( const std::string &name ) : name( name ) +// { +// int fd = shm_open( name.c_str(), O_RDWR, 0600 ); +// if( fd < 0 ) +// throw shm_error( errno ); +// struct stat statbuf; +// if( fstat( fd, &statbuf ) < 0 ) +// throw shm_error( errno ); +// size = statbuf.st_size; +// if( sizeof( T ) > size ) +// throw shm_error( EINVAL ); +// void *mem = map_shm( fd, size ); +// close( fd ); +// ptr = reinterpret_cast( mem ); +// } +// +// std::string name; //< name of the shared memory block +// T *ptr; //< the pointer to the shared object +// size_t size; //< actual size of the shared memory block +// }; +// +// /** +// * Factory for creating a shared memory block. +// * +// * @param name : name of the shared memory block (shared +// * memory object should be identified by a +// * name of the form /somename) +// * @param args : argument to be passed to constructor of +// * type T +// * @return : pointer to the shared memory +// * @throws : an instance of shm_error in case of failure +// */ +// template +// inline shm_ptr make_shm( const std::string &name, Args&&... args ) +// { +// return shm_ptr( name, sizeof( T ), std::forward( args... ) ); +// } +// +// /** +// * Factory for creating a shared memory block. +// * +// * @param name : name of the shared memory block (shared +// * memory object should be identified by a +// * name of the form /somename) +// * @return : pointer to the shared memory +// * @throws : an instance of shm_error in case of failure +// */ +// template +// inline shm_ptr make_shm( const std::string &name ) +// { +// return shm_ptr( name, sizeof( T ) ); +// } +// +// /** +// * Factory for getting an existing shared memory block. +// * +// * @param name : name of the shared memory block (shared +// * memory object should be identified by a +// * name of the form /somename) +// * @return : pointer to the shared memory +// * @throws : an instance of shm_error in case of failure +// */ +// template +// inline shm_ptr get_shm( const std::string &name ) +// { +// return shm_ptr( name ); +// } +// +// /** +// * Helper function for deleting an existing shared memory block. +// * @throws : an instance of shm_error in case of failure +// */ +// inline void rm_shm( const std::string &name ) +// { +// int rc = shm_unlink( name.c_str() ); +// if( rc < 0 ) +// throw shm_error( errno ); +// } +// +// /** +// * Helper function for calling the object destructor and +// * then destroying the shared memory block. +// * @throws : an instance of shm_error in case of failure +// */ +// template +// inline void destroy_shm( shm_ptr ptr ) +// { +// std::string name = ptr.name; +// T* obj = ptr.get(); +// obj->~T(); +// shm_ptr::consume( std::move( ptr ) ); +// int rc = shm_unlink( name.c_str() ); +// if( rc < 0 ) +// throw shm_error( errno ); +// } } From 7fa97760395df69cabbdd55ab015b8ced76559b8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 22 Feb 2023 10:51:58 +0100 Subject: [PATCH 203/773] [CI] Drop builds on Ubuntu 18 (bionic) This platform is no longer supported, as the version of CMake is too old. The version of CMake available is 3.10 while XRootD now needs CMake 3.16. --- .gitlab-ci.yml | 31 ++----------------- .../debian_scripts/publish_debian_cern.sh | 2 +- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a23eca3238e..b4aee001614 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -261,20 +261,6 @@ build:fedora-36: - web allow_failure: true -build:deb_ubuntu_bionic: - image: ubuntu:bionic - <<: *deb_ubuntu_build_def - variables: - DIST: bionic - DEBUG: "true" - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - build:deb_ubuntu_focal: image: ubuntu:focal <<: *deb_ubuntu_build_def @@ -425,17 +411,6 @@ release:cc7-x86_64: except: - branches -release:deb_ubuntu_bionic: - image: ubuntu:bionic - <<: *deb_ubuntu_build_def - variables: - DIST: bionic - DEBUG: "true" - only: - - web - except: - - branches - release:deb_ubuntu_focal: image: ubuntu:focal <<: *deb_ubuntu_build_def @@ -697,7 +672,7 @@ publish:rhel: publish:debian: stage: publish - image: ubuntu:bionic + image: ubuntu:jammy script: - apt-get update - apt-get install -y sudo apt-utils sssd gpg @@ -709,7 +684,6 @@ publish:debian: tags: - docker_node dependencies: - - build:deb_ubuntu_bionic - build:deb_ubuntu_focal - build:deb_ubuntu_jammy only: @@ -746,7 +720,7 @@ publish:rhel:release: publish:debian:release: stage: publish - image: ubuntu:bionic + image: ubuntu:jammy script: - apt-get update - apt-get install -y sudo apt-utils sssd gpg @@ -760,7 +734,6 @@ publish:debian:release: tags: - docker_node dependencies: - - release:deb_ubuntu_bionic - release:deb_ubuntu_focal - release:deb_ubuntu_jammy only: diff --git a/packaging/debian_scripts/publish_debian_cern.sh b/packaging/debian_scripts/publish_debian_cern.sh index 090f2056b31..be5bafe4baa 100755 --- a/packaging/debian_scripts/publish_debian_cern.sh +++ b/packaging/debian_scripts/publish_debian_cern.sh @@ -10,7 +10,7 @@ set -e comp=$1 prefix=/eos/project/s/storage-ci/www/debian/xrootd -for dist in bionic focal jammy; do +for dist in focal jammy; do echo "Publishing for $dist"; path=$prefix/pool/$dist/$comp/x/xrootd/; mkdir -p $path; From 0a8ec92cf32afe6dce7ef0e3a83cb2f6ab60454b Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sun, 19 Feb 2023 11:45:37 -0600 Subject: [PATCH 204/773] Denote Accept-Ranges in HEAD response Some clients (particularly, the golang library used by stashcp) will probe for byte range support by issuing a HEAD request and looking for the standard `Accept-Ranges` header in the response. Set this header in XrdHttp as we do support byte ranges. --- src/XrdHttp/XrdHttpReq.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 92b89fa76b2..ad25240df51 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -1976,7 +1976,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { if (m_req_digest.size()) { return 0; } else { - prot->SendSimpleResp(200, NULL, NULL, NULL, filesize, keepalive); + prot->SendSimpleResp(200, NULL, "Accept-Ranges: bytes", NULL, filesize, keepalive); return keepalive ? 1 : -1; } } @@ -1986,12 +1986,14 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { return keepalive ? 1 : -1; } else { // We requested a checksum and now have its response. if (iovN > 0) { - std::string digest_response; - int response = PostProcessChecksum(digest_response); + std::string response_headers; + int response = PostProcessChecksum(response_headers); if (-1 == response) { return -1; } - prot->SendSimpleResp(200, NULL, digest_response.c_str(), NULL, filesize, keepalive); + if (!response_headers.empty()) {response_headers += "\r\n";} + response_headers += "Accept-Ranges: bytes"; + prot->SendSimpleResp(200, NULL, response_headers.c_str(), NULL, filesize, keepalive); return keepalive ? 1 : -1; } else { prot->SendSimpleResp(500, NULL, NULL, "Underlying filesystem failed to calculate checksum.", 0, false); From d83e15507e673b35ff5f5620a342cf3608f4b3bb Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sun, 19 Feb 2023 11:47:15 -0600 Subject: [PATCH 205/773] Bugfix: Correct response for single byte range If an open-ended byte range is requested by the client: ``` Range: bytes=1234- ``` then the HTTP server was setting the end of the range to `-1`. In this case, once the file is opened, the end of the range should be set to `filesize - 1` (note ranges are inclusive of the end byte, meaning it looks like it's off-by-one -- that's just how the HTTP spec works). Without this, a single open-ended byte range request would fail. --- 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 ad25240df51..55a7f4f8af7 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -2308,6 +2308,8 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { } 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); char buf[64]; @@ -2315,7 +2317,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { sprintf(buf, "%lld-%lld/%lld", rwOps[0].bytestart, rwOps[0].byteend, filesize); s += buf; if (!m_digest_header.empty()) { - s += "\n"; + s += "\r\n"; s += m_digest_header.c_str(); } From c062f9888b73e4e660fa9f28f45ac521cd9540f0 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 22 Feb 2023 22:15:15 +0100 Subject: [PATCH 206/773] [XrdSys] Simplify the shm interface. --- src/XrdSys/XrdSysShmem.hh | 495 ++++---------------------------------- 1 file changed, 53 insertions(+), 442 deletions(-) diff --git a/src/XrdSys/XrdSysShmem.hh b/src/XrdSys/XrdSysShmem.hh index e925b84606f..3a85658c6fc 100644 --- a/src/XrdSys/XrdSysShmem.hh +++ b/src/XrdSys/XrdSysShmem.hh @@ -49,7 +49,7 @@ namespace XrdSys }; /** - * Factory methods for obtaining shared memory blocks + * Utility class for creating and obtaining shared emory */ struct shm { @@ -68,7 +68,10 @@ namespace XrdSys if( fd < 0 ) throw shm_error( errno ); if( ftruncate( fd, size ) < 0 ) - throw shm_error( errno ); + { + if( errno != EINVAL ) + throw shm_error( errno ); + } struct stat statbuf; if( fstat( fd, &statbuf ) < 0 ) throw shm_error( errno ); @@ -86,7 +89,8 @@ namespace XrdSys * name of the form /somename) * @return : pointer to the shared memory and its size */ - inline static std::tuple get( const std::string &name ) + template + inline static std::tuple get( const std::string &name ) { int fd = shm_open( name.c_str(), O_RDWR, 0600 ); if( fd < 0 ) @@ -97,7 +101,52 @@ namespace XrdSys size_t size = statbuf.st_size; void *mem = map_shm( fd, size ); close( fd ); - return std::make_tuple( mem, size ); + return std::make_tuple( reinterpret_cast( mem ), size ); + } + + /** + * Helper function for creating a shared memory block and + * constructing an array of objects of type T (constructed + * with default constructor) within the block. + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @param count : size of the array + * @return : pointer to the shared memory and its size + */ + template + inline static std::tuple make_array( const std::string &name, size_t count ) + { + auto tpl = create( name, count * sizeof( T ) ); + T* arr = reinterpret_cast( std::get<0>( tpl ) ); + size_t size = std::get<1>( tpl ); + for( size_t i = 0; i < count; ++i ) + new( arr + i ) T(); + return std::make_tuple( arr, size ); + } + + /** + * Helper function for creating a shared memory block and + * constructing an array of objects of type T (constructed + * with using arguments args) within the block. + * + * @param name : name of the shared memory block (shared + * memory object should be identified by a + * name of the form /somename) + * @param count : size of the array + * @param args : the arguments for the T constructor + * @return : pointer to the shared memory and its size + */ + template + inline static std::tuple make_array( const std::string &name, size_t count, Args&&... args ) + { + auto tpl = create( name, count * sizeof( T ) ); + T* arr = reinterpret_cast( std::get<0>( tpl ) ); + size_t size = std::get<1>( tpl ); + for( size_t i = 0; i < count; ++i ) + new( arr + i ) T( std::forward( args... ) ); + return std::make_tuple( arr, size ); } private: @@ -120,444 +169,6 @@ namespace XrdSys return mem; } }; - - /** - * A shared memory buffer of objects of type T - */ - template - class shm_buffer - { - public: - - /** - * Constructor. Creates shared memory block (big enough to - * accommodate `count` objects of type T) identified by - * name argument and maps it into user virtual address space - * and constructs within that memory `count` objects of type - * T using default constructor. - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - * @param count : number of object in the buffer - */ - shm_buffer( const std::string &name, size_t count ) : - name( name ), count( count ) - { - void *ptr; - std::tie( ptr, size ) = shm::create( name, count * sizeof(T) ); - buff = reinterpret_cast( ptr ); - for( size_t i = 0; i < count; ++i ) - new( buff + i ) T(); - } - - /** - * Constructor. Creates shared memory block (big enough to - * accommodate `count` objects of type T) identified by - * name argument and maps it into user virtual address space - * and constructs within that memory `count` objects of type - * T using arguments args - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - * @param count : number of object in the buffer - */ - template - shm_buffer( const std::string &name, size_t count, Args&&... args ) : - name( name ), count( count ) - { - void *ptr; - std::tie( ptr, size ) = shm::create( name, count * sizeof(T) ); - buff = reinterpret_cast( ptr ); - for( size_t i = 0; i < count; ++i ) - new( buff + i ) T( args... ); - } - - /** - * Constructor for initializing from existing shared memory block. - * - * @param name : name of the shared memory block (shared - * memory object should be identified by a - * name of the form /somename) - */ - shm_buffer( const std::string &name ) : name( name ) - { - void *ptr; - std::tie( ptr, size ) = shm::get( name ); - buff = reinterpret_cast( ptr ); - } - - /** - * Move constructor - */ - shm_buffer( shm_buffer && mv ) : - name( std::move( mv.name ) ), - count( mv.count ), - size( mv.size ), - buff( mv.buff ) - { - mv.count = 0; - mv.size = 0; - mv.buff = nullptr; - } - - /** - * Destructor - */ - ~shm_buffer() - { - reset(); - } - - /** - * Move assigment operator - */ - shm_buffer& operator=( shm_buffer && mv ) - { - name = std::move( mv.name ); - count = mv.count; - size = mv.size; - buff = mv.buff; - mv.count = 0; - mv.size = 0; - mv.buff = nullptr; - } - - /** - * Subscript operator - */ - T& operator[]( size_t idx ) - { - return buff[idx]; - } - - /** - * Subscript operator (const) - */ - const T& operator[]( size_t idx ) const - { - return buff[idx]; - } - - /** - * Unmap the block from the user virtual memory space - * and reset the object - */ - inline void reset() - { - if( buff ) - munmap( buff, size ); - name.clear(); - count = 0; - size = 0; - buff = nullptr; - } - - /** - * Release the ownership of the shared memory block - * - * @return : the shared memory block - */ - inline T* release() - { - T *ptr = buff; - name.clear(); - count = 0; - size = 0; - buff = nullptr; - return ptr; - } - - /** - * Finalize the object and destroy the associated - * shared memory block. - * - * @param buffer : the object to be destroyed - */ - inline static void destroy( shm_buffer && buffer ) - { - std::string name = buffer.name; - for( size_t i = 0; i < buffer.count; ++i ) - buffer.buff[i].~T(); - buffer.reset(); - int rc = shm_unlink( name.c_str() ); - if( rc < 0 ) - throw shm_error( errno ); - } - - private: - std::string name; //< shared memory block name - size_t count; //< object count in the buffer - size_t size; //< the size of the shared memory block - T* buff; //< pointer to the shared memory block - }; - -// -// -// /** -// * Smart pointer for encapsulating shared memory segments. -// * The destructor will automatically unmap the memory from -// * user's virtual address space. -// * -// * It is user responsibility to finalize the T object -// * (the shm_ptr does not ensure calling a destructor -// * for the object). -// */ -// template -// class shm_ptr -// { -// /// Friend factory methods -// template -// friend shm_ptr make_shm( const std::string&, Args... ); -// template -// friend shm_ptr make_shm( const std::string& ); -// template -// friend shm_ptr get_shm( const std::string& ); -// template -// friend void destroy_shm( shm_ptr ); -// -// public: -// -// /// Move constructor -// shm_ptr( shm_ptr &&mv ) : -// name( std::move( mv.name ) ), ptr( mv.ptr ), size( mv.size ) -// { -// mv.ptr = nullptr; -// mv.size = 0; -// } -// -// /// Move assignment operator -// shm_ptr& operator=( shm_ptr &&mv ) -// { -// ptr = mv.ptr; -// size = mv.size; -// name = std::move( mv.name ); -// mv.ptr = nullptr; -// mv.size = 0; -// return *this; -// } -// -// /// Destructor -// ~shm_ptr() -// { -// if( ptr ) -// munmap( ptr, size ); -// } -// -// /// Member access operator -// T* operator->() { return ptr; } -// -// /// Member access operator (const) -// const T* operator->() const { return ptr; } -// -// /// Dereferencing operator -// T& operator*() { return *ptr; } -// -// /// Dereferencing operator (const) -// const T& operator*() const { return *ptr; } -// -// /// @return : the raw pointer to the shared memory block -// inline T* get() -// { -// return ptr; -// } -// -// /// @return : the actual size of the shared memory block -// inline size_t get_size() -// { -// return size; -// } -// -// /// release the ownership of the shared memory block -// inline T* release() -// { -// T* ret = ptr; -// ptr = nullptr; -// size = 0; -// return ret; -// } -// -// private: -// -// /** -// * Helper function for mapping the shared memory block -// * into user virtual address space -// * -// * @param fd : handle to the shared memory segment -// * @param size : size of the shared memory segment -// * @return : pointer to the shared memory -// */ -// inline static void* map_shm( int fd, size_t size ) -// { -// void *mem = mmap( nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); -// if( mem == MAP_FAILED ) -// { -// mem = nullptr; -// throw shm_error( errno ); -// } -// return mem; -// } -// -// /** -// * Helper function for creating shared memory block -// * -// * @param name : name of the shared memory block (shared -// * memory object should be identified by a -// * name of the form /somename) -// * @param size : size of the shared memory segment -// * @return : pointer to the shared memory -// */ -// inline static std::tuple create_shm( const std::string &name, size_t size ) -// { -// int fd = shm_open( name.c_str(), O_CREAT | O_RDWR, 0600 ); -// if( fd < 0 ) -// throw shm_error( errno ); -// if( ftruncate( fd, size ) < 0 ) -// throw shm_error( errno ); -// struct stat statbuf; -// if( fstat( fd, &statbuf ) < 0 ) -// throw shm_error( errno ); -// size = statbuf.st_size; -// void *mem = map_shm( fd, size ); -// close( fd ); -// return std::make_tuple( mem, size ); -// } -// -// /** -// * Consumes a shm_ptr -// */ -// inline static void consume( shm_ptr ptr ) { } -// -// /** -// * Constructor. Creates shared memory block (of size at least -// * size) identified by name argument and maps it into user -// * virtual address space and constructs within that memory -// * object of type T using arguments args. -// */ -// template -// shm_ptr( const std::string &name, size_t size, Args&&... args ) : name( name ) -// { -// void *mem = nullptr; -// std::tie( mem, size ) = create_shm( name, size ); -// ptr = new( mem ) T( std::forward( args... ) ); -// } -// -// /** -// * Constructor. Creates shared memory block (of size at least -// * size) identified by name argument and maps it into user -// * virtual address space and constructs within that memory -// * object of type T using default constructor. -// */ -// shm_ptr( const std::string &name, size_t size ) : name( name ) -// { -// void *mem = nullptr; -// std::tie( mem, size ) = create_shm( name, size ); -// ptr = new( mem ) T(); -// } -// -// /** -// * Constructor for initializing from existing shared memory block. -// * -// * @param name : name of the shared memory block (shared -// * memory object should be identified by a -// * name of the form /somename) -// */ -// shm_ptr( const std::string &name ) : name( name ) -// { -// int fd = shm_open( name.c_str(), O_RDWR, 0600 ); -// if( fd < 0 ) -// throw shm_error( errno ); -// struct stat statbuf; -// if( fstat( fd, &statbuf ) < 0 ) -// throw shm_error( errno ); -// size = statbuf.st_size; -// if( sizeof( T ) > size ) -// throw shm_error( EINVAL ); -// void *mem = map_shm( fd, size ); -// close( fd ); -// ptr = reinterpret_cast( mem ); -// } -// -// std::string name; //< name of the shared memory block -// T *ptr; //< the pointer to the shared object -// size_t size; //< actual size of the shared memory block -// }; -// -// /** -// * Factory for creating a shared memory block. -// * -// * @param name : name of the shared memory block (shared -// * memory object should be identified by a -// * name of the form /somename) -// * @param args : argument to be passed to constructor of -// * type T -// * @return : pointer to the shared memory -// * @throws : an instance of shm_error in case of failure -// */ -// template -// inline shm_ptr make_shm( const std::string &name, Args&&... args ) -// { -// return shm_ptr( name, sizeof( T ), std::forward( args... ) ); -// } -// -// /** -// * Factory for creating a shared memory block. -// * -// * @param name : name of the shared memory block (shared -// * memory object should be identified by a -// * name of the form /somename) -// * @return : pointer to the shared memory -// * @throws : an instance of shm_error in case of failure -// */ -// template -// inline shm_ptr make_shm( const std::string &name ) -// { -// return shm_ptr( name, sizeof( T ) ); -// } -// -// /** -// * Factory for getting an existing shared memory block. -// * -// * @param name : name of the shared memory block (shared -// * memory object should be identified by a -// * name of the form /somename) -// * @return : pointer to the shared memory -// * @throws : an instance of shm_error in case of failure -// */ -// template -// inline shm_ptr get_shm( const std::string &name ) -// { -// return shm_ptr( name ); -// } -// -// /** -// * Helper function for deleting an existing shared memory block. -// * @throws : an instance of shm_error in case of failure -// */ -// inline void rm_shm( const std::string &name ) -// { -// int rc = shm_unlink( name.c_str() ); -// if( rc < 0 ) -// throw shm_error( errno ); -// } -// -// /** -// * Helper function for calling the object destructor and -// * then destroying the shared memory block. -// * @throws : an instance of shm_error in case of failure -// */ -// template -// inline void destroy_shm( shm_ptr ptr ) -// { -// std::string name = ptr.name; -// T* obj = ptr.get(); -// obj->~T(); -// shm_ptr::consume( std::move( ptr ) ); -// int rc = shm_unlink( name.c_str() ); -// if( rc < 0 ) -// throw shm_error( errno ); -// } } From 3842019e235f2028779d2b55098d03c01cee83e5 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 21 Feb 2023 14:27:23 +0100 Subject: [PATCH 207/773] [CMake] Remove option BUILD_CRYPTO/ENABLE_CRYPTO The option ENABLE_CRYPTO cannot be disabled (XRootD fails to build). Therefore, we remove it and make OpenSSL a required dependency. Fixes #1827. --- README | 2 - cmake/XRootDDefaults.cmake | 1 - cmake/XRootDFindLibs.cmake | 35 ++++------------ cmake/XRootDSummary.cmake | 1 - src/CMakeLists.txt | 5 +-- src/XrdCrypto.cmake | 86 +++++++++++++++++--------------------- src/XrdSec.cmake | 17 +++----- 7 files changed, 52 insertions(+), 95 deletions(-) diff --git a/README b/README index f45c2ff9e06..4df5d8ff7b2 100644 --- a/README +++ b/README @@ -44,8 +44,6 @@ is neither recommended nor supported. * 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 diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index 1bef0cce3be..f9bccf6549d 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -13,7 +13,6 @@ include( CMakeDependentOption ) define_default( PLUGIN_VERSION 5 ) option( ENABLE_FUSE "Enable the fuse filesystem driver if possible." TRUE ) -option( ENABLE_CRYPTO "Enable the OpenSSL cryprography support." TRUE ) option( ENABLE_KRB5 "Enable the Kerberos 5 authentication if possible." TRUE ) option( ENABLE_READLINE "Enable the lib readline support in the commandline utilities." TRUE ) option( ENABLE_XRDCL "Enable XRootD client." TRUE ) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index f88e871c983..6ee71c5712d 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -37,21 +37,10 @@ endif() find_package( CURL ) -if( ENABLE_CRYPTO ) - if( FORCE_ENABLED ) - find_package( OpenSSL 1.0.2 REQUIRED ) - else() - find_package( OpenSSL 1.0.2 ) - endif() - if( OPENSSL_FOUND ) - add_definitions( -DHAVE_DH_PADDED ) - add_definitions( -DHAVE_XRDCRYPTO ) - add_definitions( -DHAVE_SSL ) - set( BUILD_CRYPTO TRUE ) - else() - set( BUILD_CRYPTO FALSE ) - endif() -endif() +find_package( OpenSSL 1.0.2 REQUIRED ) +add_definitions( -DHAVE_DH_PADDED ) +add_definitions( -DHAVE_XRDCRYPTO ) +add_definitions( -DHAVE_SSL ) if( ENABLE_KRB5 ) if( FORCE_ENABLED ) @@ -95,21 +84,13 @@ if( ENABLE_TESTS ) endif() if( ENABLE_HTTP ) - if( OPENSSL_FOUND AND BUILD_CRYPTO ) - set( BUILD_HTTP TRUE ) - if( CURL_FOUND ) - set( BUILD_TPC TRUE ) - else() - if( FORCE_ENABLED ) - message( FATAL_ERROR "Cannot build HttpTpc: missing CURL." ) - endif() - set( BUILD_TPC FALSE ) - endif() + set( BUILD_HTTP TRUE ) + if( CURL_FOUND ) + set( BUILD_TPC TRUE ) else() if( FORCE_ENABLED ) - message( FATAL_ERROR "Cannot build XrdHttp: missing OpenSSL." ) + message( FATAL_ERROR "Cannot build HttpTpc: missing CURL." ) endif() - set( BUILD_HTTP FALSE ) set( BUILD_TPC FALSE ) endif() endif() diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index 7874756f91d..94ba80fac4d 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -4,7 +4,6 @@ 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( XRDCLHTTP ENABLE_XRDCLHTTP DAVIX_FOUND ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6236c03c7ed..abe7cb2a5ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,10 +24,7 @@ include( XrdPosix ) include( XrdSec ) include( XrdXml ) include( XrdHeaders ) - -if( BUILD_CRYPTO ) - include( XrdSecgsi ) -endif() +include( XrdSecgsi ) if( BUILD_KRB5 ) include( XrdSeckrb5 ) diff --git a/src/XrdCrypto.cmake b/src/XrdCrypto.cmake index bf9d8f99cb4..4aaba96a82b 100644 --- a/src/XrdCrypto.cmake +++ b/src/XrdCrypto.cmake @@ -63,16 +63,10 @@ add_library( SHARED ${XrdCryptoLiteSources} ) -if( BUILD_CRYPTO ) - target_link_libraries( - XrdCryptoLite - XrdUtils - ${OPENSSL_CRYPTO_LIBRARY} ) -else() - target_link_libraries( - XrdCryptoLite - XrdUtils ) -endif() +target_link_libraries( + XrdCryptoLite + XrdUtils + ${OPENSSL_CRYPTO_LIBRARY} ) set_target_properties( XrdCryptoLite @@ -85,39 +79,37 @@ set_target_properties( #------------------------------------------------------------------------------- # The XrdCryptossl module #------------------------------------------------------------------------------- -if( BUILD_CRYPTO ) - include_directories( ${OPENSSL_INCLUDE_DIR} ) - - set( XrdCryptosslSources - XrdCrypto/XrdCryptosslAux.cc XrdCrypto/XrdCryptosslAux.hh - XrdCrypto/XrdCryptosslgsiAux.cc - XrdCrypto/XrdCryptosslCipher.cc XrdCrypto/XrdCryptosslCipher.hh - XrdCrypto/XrdCryptosslMsgDigest.cc XrdCrypto/XrdCryptosslMsgDigest.hh - XrdCrypto/XrdCryptosslRSA.cc XrdCrypto/XrdCryptosslRSA.hh - XrdCrypto/XrdCryptosslX509.cc XrdCrypto/XrdCryptosslX509.hh - XrdCrypto/XrdCryptosslX509Crl.cc XrdCrypto/XrdCryptosslX509Crl.hh - XrdCrypto/XrdCryptosslX509Req.cc XrdCrypto/XrdCryptosslX509Req.hh - XrdCrypto/XrdCryptosslTrace.hh - XrdCrypto/XrdCryptosslFactory.cc XrdCrypto/XrdCryptosslFactory.hh ) - - add_library( - ${LIB_XRD_CRYPTOSSL} - MODULE - ${XrdCryptosslSources} ) - - target_link_libraries( - ${LIB_XRD_CRYPTOSSL} - XrdCrypto - XrdUtils - ${CMAKE_THREAD_LIBS_INIT} - ${OPENSSL_LIBRARIES} ) - - set_target_properties( - ${LIB_XRD_CRYPTOSSL} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) -endif() +include_directories( ${OPENSSL_INCLUDE_DIR} ) + +set( XrdCryptosslSources + XrdCrypto/XrdCryptosslAux.cc XrdCrypto/XrdCryptosslAux.hh + XrdCrypto/XrdCryptosslgsiAux.cc + XrdCrypto/XrdCryptosslCipher.cc XrdCrypto/XrdCryptosslCipher.hh + XrdCrypto/XrdCryptosslMsgDigest.cc XrdCrypto/XrdCryptosslMsgDigest.hh + XrdCrypto/XrdCryptosslRSA.cc XrdCrypto/XrdCryptosslRSA.hh + XrdCrypto/XrdCryptosslX509.cc XrdCrypto/XrdCryptosslX509.hh + XrdCrypto/XrdCryptosslX509Crl.cc XrdCrypto/XrdCryptosslX509Crl.hh + XrdCrypto/XrdCryptosslX509Req.cc XrdCrypto/XrdCryptosslX509Req.hh + XrdCrypto/XrdCryptosslTrace.hh + XrdCrypto/XrdCryptosslFactory.cc XrdCrypto/XrdCryptosslFactory.hh ) + +add_library( + ${LIB_XRD_CRYPTOSSL} + MODULE + ${XrdCryptosslSources} ) + +target_link_libraries( + ${LIB_XRD_CRYPTOSSL} + XrdCrypto + XrdUtils + ${CMAKE_THREAD_LIBS_INIT} + ${OPENSSL_LIBRARIES} ) + +set_target_properties( + ${LIB_XRD_CRYPTOSSL} + PROPERTIES + INTERFACE_LINK_LIBRARIES "" + LINK_INTERFACE_LIBRARIES "" ) #------------------------------------------------------------------------------- # Install @@ -126,11 +118,9 @@ install( TARGETS XrdCrypto XrdCryptoLite LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) -if( BUILD_CRYPTO ) - install( - TARGETS ${LIB_XRD_CRYPTOSSL} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) -endif() +install( + TARGETS ${LIB_XRD_CRYPTOSSL} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) # FIXME: Unused files #-rw-r--r-- 1 ljanyst ljanyst 16721 2011-03-21 16:13 XrdCryptotest.cc diff --git a/src/XrdSec.cmake b/src/XrdSec.cmake index 2f5bf0068b1..e17e923a989 100644 --- a/src/XrdSec.cmake +++ b/src/XrdSec.cmake @@ -56,18 +56,11 @@ add_library( MODULE ${XrdSecProtectSources} ) -if( BUILD_CRYPTO ) - target_link_libraries( - ${LIB_XRD_SEC_PROT} - XrdUtils - ${CMAKE_THREAD_LIBS_INIT} - ${OPENSSL_CRYPTO_LIBRARY} ) -else() - target_link_libraries( - ${LIB_XRD_SEC_PROT} - XrdUtils - ${CMAKE_THREAD_LIBS_INIT} ) -endif() +target_link_libraries( + ${LIB_XRD_SEC_PROT} + XrdUtils + ${CMAKE_THREAD_LIBS_INIT} + ${OPENSSL_CRYPTO_LIBRARY} ) set_target_properties( ${LIB_XRD_SEC_PROT} From 56b2bdee0af49cc33e359a1bb8a170d0ed84f40e Mon Sep 17 00:00:00 2001 From: Brian P Bockelman Date: Thu, 23 Feb 2023 03:10:32 -0600 Subject: [PATCH 208/773] Allow XRootD to return trailers indicating failure (#1912) * Allow XRootD to return trailers indicating failure HTTP provides a response to include a "trailer" in addition to the better-known "header". If a user sets the following headers in the request: ``` X-Transfer-Status: true TE: trailers Transfer-Encoding: chunked ``` Then the response will used chunked encoding and indicate, on the last returned chunk, whether an error has occurred. Clients aware of these headers can now receive an error message from XRootD if there's an IO error in the middle of the response. This is expected to be useful in XCache use cases where failure mid-response is somewhat more common. * Fixup: Also send trailers as requested for byte range reads Additionally does some modest tidying of comments. --- src/XrdHttp/XrdHttpExtHandler.cc | 2 +- src/XrdHttp/XrdHttpProtocol.cc | 37 +++++++++---- src/XrdHttp/XrdHttpProtocol.hh | 13 ++++- src/XrdHttp/XrdHttpReq.cc | 94 ++++++++++++++++++++++++++++---- src/XrdHttp/XrdHttpReq.hh | 8 +++ 5 files changed, 127 insertions(+), 27 deletions(-) diff --git a/src/XrdHttp/XrdHttpExtHandler.cc b/src/XrdHttp/XrdHttpExtHandler.cc index c409b0ea7ae..7448331bfca 100644 --- a/src/XrdHttp/XrdHttpExtHandler.cc +++ b/src/XrdHttp/XrdHttpExtHandler.cc @@ -52,7 +52,7 @@ int XrdHttpExtReq::StartChunkedResp(int code, const char *desc, const char *head { if (!prot) return -1; - return prot->StartChunkedResp(code, desc, header_to_add, true); + return prot->StartChunkedResp(code, desc, header_to_add, -1, true); } int XrdHttpExtReq::ChunkResp(const char *body, long long bodylen) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index f9b8f0dd4c4..40c455656e5 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1545,7 +1545,7 @@ int XrdHttpProtocol::StartSimpleResp(int code, const char *desc, const char *hea /* S t a r t C h u n k e d R e s p */ /******************************************************************************/ -int XrdHttpProtocol::StartChunkedResp(int code, const char *desc, const char *header_to_add, bool keepalive) { +int XrdHttpProtocol::StartChunkedResp(int code, const char *desc, const char *header_to_add, long long bodylen, bool keepalive) { const std::string crlf = "\r\n"; std::stringstream ss; @@ -1555,7 +1555,7 @@ int XrdHttpProtocol::StartChunkedResp(int code, const char *desc, const char *he ss << "Transfer-Encoding: chunked"; TRACEI(RSP, "Starting chunked response"); - return StartSimpleResp(code, desc, ss.str().c_str(), -1, keepalive); + return StartSimpleResp(code, desc, ss.str().c_str(), bodylen, keepalive); } /******************************************************************************/ @@ -1563,23 +1563,36 @@ 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)) + return -1; + + if (body && SendData(body, bodylen)) + return -1; + + return ChunkRespFooter(); +} + +/******************************************************************************/ +/* C h u n k R e s p H e a d e r */ +/******************************************************************************/ + +int XrdHttpProtocol::ChunkRespHeader(long long bodylen) { const std::string crlf = "\r\n"; - long long chunk_length = bodylen; - if (bodylen <= 0) { - chunk_length = body ? strlen(body) : 0; - } std::stringstream ss; - ss << std::hex << chunk_length << std::dec << crlf; + ss << std::hex << bodylen << std::dec << crlf; const std::string &chunkhdr = ss.str(); - TRACEI(RSP, "Sending encoded chunk of size " << chunk_length); - if (SendData(chunkhdr.c_str(), chunkhdr.size())) - return -1; + TRACEI(RSP, "Sending encoded chunk of size " << bodylen); + return (SendData(chunkhdr.c_str(), chunkhdr.size())) ? -1 : 0; +} - if (body && SendData(body, chunk_length)) - return -1; +/******************************************************************************/ +/* C h u n k R e s p F o o t e r */ +/******************************************************************************/ +int XrdHttpProtocol::ChunkRespFooter() { + const std::string crlf = "\r\n"; return (SendData(crlf.c_str(), crlf.size())) ? -1 : 0; } diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index ee4f975d841..7a537c9ab30 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -271,15 +271,22 @@ private: /// Starts a chunked response; body of request is sent over multiple parts using the SendChunkResp // API. - int StartChunkedResp(int code, const char *desc, const char *header_to_add, bool keepalive); + int StartChunkedResp(int code, const char *desc, const char *header_to_add, long long bodylen, bool keepalive); /// Send a (potentially partial) body in a chunked response; invoking with NULL body // indicates that this is the last chunk in the response. int ChunkResp(const char *body, long long bodylen); - + + /// Send the beginning of a chunked response but not the body; useful when the size + // of the chunk is known but the body is not immediately available. + int ChunkRespHeader(long long bodylen); + + /// Send the footer of the chunk response + int ChunkRespFooter(); + /// Gets a string that represents the IP address of the client. Must be freed char *GetClientIPStr(); - + /// Tells that we are just logging in bool DoingLogin; diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 55a7f4f8af7..6cffac70b50 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -298,6 +298,10 @@ int XrdHttpReq::parseLine(char *line, int len) { 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_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); @@ -1050,6 +1054,9 @@ void XrdHttpReq::mapXrdErrorToHttpStatus() { << "] to status code [" << httpStatusCode << "]"); httpStatusText += "\n"; + } else { + httpStatusCode = 200; + httpStatusText = "OK"; } } @@ -1420,7 +1427,9 @@ int XrdHttpReq::ProcessHTTPReq() { xrdreq.read.rlen = htonl(l); } - if (prot->ishttps) { + // 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 (!prot->Bridge->setSF((kXR_char *) fhandle, false)) { TRACE(REQ, " XrdBridge::SetSF(false) failed."); @@ -2302,8 +2311,12 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { if (rwOps.size() == 0) { // Full file. - - prot->SendSimpleResp(200, NULL, m_digest_header.empty() ? NULL : m_digest_header.c_str(), NULL, filesize, keepalive); + + if (m_transfer_encoding_chunked && m_trailer_headers) { + prot->StartChunkedResp(200, NULL, m_digest_header.empty() ? NULL : m_digest_header.c_str(), filesize, keepalive); + } else { + prot->SendSimpleResp(200, NULL, m_digest_header.empty() ? NULL : m_digest_header.c_str(), NULL, filesize, keepalive); + } return 0; } else if (rwOps.size() == 1) { @@ -2372,11 +2385,59 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { default: //read or readv { - // Nothing to do if we are postprocessing a close - if (ntohs(xrdreq.header.requestid) == kXR_close) return keepalive ? 1 : -1; - - // Close() if this was the third state of a readv, otherwise read the next chunk - if ((reqstate == 3) && (ntohs(xrdreq.header.requestid) == kXR_readv)) return keepalive ? 1: -1; + // 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))) + { + + if (m_transfer_encoding_chunked && m_trailer_headers) { + if (prot->ChunkRespHeader(0)) + return -1; + + const std::string crlf = "\r\n"; + std::stringstream ss; + ss << "X-Transfer-Status: " << httpStatusCode << ": " << httpStatusText << crlf; + + const auto header = ss.str(); + if (prot->SendData(header.c_str(), header.size())) + return -1; + + if (prot->ChunkRespFooter()) + return -1; + } + + return keepalive ? 1 : -1; + } + + // On error, we can only send out a message if trailers are enabled and the + // status response in trailer behavior is requested. + if (xrdresp == kXR_error) { + if (m_transfer_encoding_chunked && m_trailer_headers && m_status_trailer) { + // A trailer header is appropriate in this case; this is signified by + // a chunk with size zero, then the trailer, then a crlf. + // + // We only send the status trailer when explicitly requested; otherwise a + // "normal" HTTP client might simply see a short response and think it's a + // success + if (prot->ChunkRespHeader(0)) + return -1; + + const std::string crlf = "\r\n"; + std::stringstream ss; + ss << "X-Transfer-Status: " << httpStatusCode << ": " << httpStatusText << crlf; + + const auto header = ss.str(); + if (prot->SendData(header.c_str(), header.size())) + return -1; + + if (prot->ChunkRespFooter()) + return -1; + + return -1; + } else { + return -1; + } + } // Prevent scenario where data is expected but none is actually read // E.g. Accessing files which return the results of a script @@ -2387,8 +2448,6 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { return -1; } - // If we are here it's too late to send a proper error message... - if (xrdresp == kXR_error) return -1; TRACEI(REQ, "Got data vectors to send:" << iovN); if (ntohs(xrdreq.header.requestid) == kXR_readv) { @@ -2438,11 +2497,21 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { if (prot->SendData((char *) s.c_str(), s.size())) return -1; } - } else + } 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(); + } + } // Let's make sure that we avoid sending the same data twice, // in the case where PostProcessHTTPReq is invoked again @@ -2908,6 +2977,9 @@ void XrdHttpReq::reset() { m_current_chunk_size = -1; m_current_chunk_offset = 0; + m_trailer_headers = false; + m_status_trailer = false; + /// State machine to talk to the bridge reqstate = 0; diff --git a/src/XrdHttp/XrdHttpReq.hh b/src/XrdHttp/XrdHttpReq.hh index e3b13cae3a1..797d943527a 100644 --- a/src/XrdHttp/XrdHttpReq.hh +++ b/src/XrdHttp/XrdHttpReq.hh @@ -85,6 +85,14 @@ private: long long m_current_chunk_offset; long long m_current_chunk_size; + // Whether trailer headers were enabled + bool m_trailer_headers{false}; + + // Whether the client understands our special status trailer. + // The status trailer allows us to report when an IO error occurred + // after a response body has started + bool m_status_trailer{false}; + int parseContentRange(char *); int parseHost(char *); int parseRWOp(char *); From 4ef0f2f388f877d0715ffc345afe1b988398749b Mon Sep 17 00:00:00 2001 From: Andreas-Joachim Peters Date: Thu, 23 Feb 2023 10:26:43 +0100 Subject: [PATCH 209/773] [XrdSecztn] Allow to point to a token file using CGI '?xrd.ztn=tokenfile' (#1926) --- src/XrdSecztn/XrdSecProtocolztn.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/XrdSecztn/XrdSecProtocolztn.cc b/src/XrdSecztn/XrdSecProtocolztn.cc index a4d8f6c53c0..c6c3ac71fea 100644 --- a/src/XrdSecztn/XrdSecProtocolztn.cc +++ b/src/XrdSecztn/XrdSecProtocolztn.cc @@ -51,6 +51,7 @@ #include "XrdVersion.hh" #include "XrdNet/XrdNetAddrInfo.hh" +#include "XrdOuc/XrdOucEnv.hh" #include "XrdOuc/XrdOucErrInfo.hh" #include "XrdOuc/XrdOucPinLoader.hh" #include "XrdOuc/XrdOucString.hh" @@ -348,6 +349,16 @@ XrdSecCredentials *XrdSecProtocolztn::findToken(XrdOucErrInfo *erp, if ((bTok = Strip(aTok, sz))) return retToken(erp, bTok, sz); } +// We support passing the credential cache path via Url parameter +// + char *ccn = (erp && erp->getEnv()) ? erp->getEnv()->Get("xrd.ztn") : 0; + if (ccn) + { + resp = readToken(erp, ccn, isbad); + if (resp || isbad) return resp; + } + +// Look through all of the possible envars // Nothing found // isbad = false; From 2c0b531a74f5b1033052333c65c67e8858c11893 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 18 Feb 2023 17:27:51 -0600 Subject: [PATCH 210/773] Report cache object age for caching proxy mode When in caching proxy mode, have the HTTP response populate the standardized `Age` field so the client knows the approximate age of the object in cache. --- src/XrdHttp/XrdHttpReq.cc | 63 +++++++++++++++++++------------ src/XrdHttp/XrdHttpReq.hh | 1 + src/XrdPfc/XrdPfcConfiguration.cc | 3 ++ src/XrdPfc/XrdPfcIOFile.cc | 7 +++- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 6cffac70b50..05a5781d60e 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -2286,36 +2286,51 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { getfhandle(); - // Now parse the stat info if we still don't have it - if (filesize == 0) { - if (iovP[1].iov_len > 1) { - TRACEI(REQ, "Stat for GET " << resource.c_str() - << " stat=" << (char *) iovP[1].iov_base); - - long dummyl; - sscanf((const char *) iovP[1].iov_base, "%ld %lld %ld %ld", - &dummyl, - &filesize, - &fileflags, - &filemodtime); + // Always try to parse response. In the case of a caching proxy, the open + // will have created the file in cache + filectime = 0; + if (iovP[1].iov_len > 1) { + TRACEI(REQ, "Stat for GET " << resource.c_str() + << " stat=" << (char *) iovP[1].iov_base); - // As above: if the client specified a response size, we use that. - // Otherwise, utilize the filesize - if (!length) { - length = filesize; - } + long dummyl; + sscanf((const char *) iovP[1].iov_base, "%ld %lld %ld %ld %ld", + &dummyl, + &filesize, + &fileflags, + &filemodtime, + &filectime); + + // As above: if the client specified a response size, we use that. + // Otherwise, utilize the filesize + if (!length) { + length = filesize; } - else - TRACEI(ALL, "GET returned no STAT information. Internal error?"); } - + else { + TRACEI(ALL, "GET returned no STAT information. Internal error?"); + } + + std::string responseHeader; + if (!m_digest_header.empty()) { + responseHeader = m_digest_header; + } + long one; + if (filemodtime && XrdOucEnv::Import("XRDPFC", one)) { + if (!responseHeader.empty()) { + responseHeader += "\r\n"; + } + long object_age = time(NULL) - filectime; + responseHeader += std::string("Age: ") + std::to_string(object_age < 0 ? 0 : object_age); + } + if (rwOps.size() == 0) { // Full file. if (m_transfer_encoding_chunked && m_trailer_headers) { - prot->StartChunkedResp(200, NULL, m_digest_header.empty() ? NULL : m_digest_header.c_str(), filesize, keepalive); + prot->StartChunkedResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), filesize, keepalive); } else { - prot->SendSimpleResp(200, NULL, m_digest_header.empty() ? NULL : m_digest_header.c_str(), NULL, filesize, keepalive); + prot->SendSimpleResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), NULL, filesize, keepalive); } return 0; } else @@ -2329,9 +2344,9 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { XrdOucString s = "Content-Range: bytes "; sprintf(buf, "%lld-%lld/%lld", rwOps[0].bytestart, rwOps[0].byteend, filesize); s += buf; - if (!m_digest_header.empty()) { + if (!responseHeader.empty()) { s += "\r\n"; - s += m_digest_header.c_str(); + s += responseHeader.c_str(); } prot->SendSimpleResp(206, NULL, (char *)s.c_str(), NULL, cnt, keepalive); diff --git a/src/XrdHttp/XrdHttpReq.hh b/src/XrdHttp/XrdHttpReq.hh index 797d943527a..f6dbdf9c5af 100644 --- a/src/XrdHttp/XrdHttpReq.hh +++ b/src/XrdHttp/XrdHttpReq.hh @@ -274,6 +274,7 @@ public: long long filesize; long fileflags; long filemodtime; + long filectime; char fhandle[4]; bool fopened; diff --git a/src/XrdPfc/XrdPfcConfiguration.cc b/src/XrdPfc/XrdPfcConfiguration.cc index c77f4af8226..0dcb4f5e5ee 100644 --- a/src/XrdPfc/XrdPfcConfiguration.cc +++ b/src/XrdPfc/XrdPfcConfiguration.cc @@ -274,6 +274,9 @@ bool Cache::Config(const char *config_filename, const char *parameters) const char *theINS = getenv("XRDINSTANCE"); m_isClient = (theINS != 0 && strncmp("*client ", theINS, 8) == 0); + // Tell everyone else we are a caching proxy + XrdOucEnv::Export("XRDPFC", 1); + XrdOucEnv myEnv; XrdOucStream Config(&m_log, theINS, &myEnv, "=====> "); diff --git a/src/XrdPfc/XrdPfcIOFile.cc b/src/XrdPfc/XrdPfcIOFile.cc index 5c7e962fbef..da0dfb1085c 100644 --- a/src/XrdPfc/XrdPfcIOFile.cc +++ b/src/XrdPfc/XrdPfcIOFile.cc @@ -83,7 +83,7 @@ int IOFile::initCachedStat(const char* path) int res = -1; struct stat tmpStat; - if (m_cache.GetOss()->Stat(path, &tmpStat) == XrdOssOK) + if (m_cache.GetOss()->Stat(GetFilename().c_str(), &tmpStat) == XrdOssOK) { XrdOssDF* infoFile = m_cache.GetOss()->newFile(Cache::GetInstance().RefConfiguration().m_username.c_str()); XrdOucEnv myEnv; @@ -115,6 +115,11 @@ int IOFile::initCachedStat(const char* path) { res = GetInput()->Fstat(tmpStat); TRACEIO(Debug, trace_pfx << "got stat from client res = " << res << ", size = " << tmpStat.st_size); + // The mtime / atime / ctime for cached responses come from the file on disk in the cache hit case. + // To avoid weirdness when two subsequent stat queries can give wildly divergent times (one from the + // origin, one from the cache), set the times to "now" so we effectively only report the *time as the + // cache service sees it. + tmpStat.st_ctime = tmpStat.st_mtime = tmpStat.st_atime = time(NULL); } if (res == 0) From 0d74d6afc7a189e49e089d119c394f368e1d52d5 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Wed, 22 Feb 2023 08:11:28 -0600 Subject: [PATCH 211/773] Cleanup items from code review - Switch to the cinfo file's creation time to use for the age attribute, not the file's ctime. - Cleanup the initCacheFile signature; input argument is not necessary. - Tune up some debug messages. --- src/XrdHttp/XrdHttpReq.cc | 8 +++----- src/XrdPfc/XrdPfcIOFile.cc | 20 ++++++++++++-------- src/XrdPfc/XrdPfcIOFile.hh | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 05a5781d60e..6732578cdfb 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -2288,18 +2288,16 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { // Always try to parse response. In the case of a caching proxy, the open // will have created the file in cache - filectime = 0; if (iovP[1].iov_len > 1) { TRACEI(REQ, "Stat for GET " << resource.c_str() << " stat=" << (char *) iovP[1].iov_base); long dummyl; - sscanf((const char *) iovP[1].iov_base, "%ld %lld %ld %ld %ld", + sscanf((const char *) iovP[1].iov_base, "%ld %lld %ld %ld", &dummyl, &filesize, &fileflags, - &filemodtime, - &filectime); + &filemodtime); // As above: if the client specified a response size, we use that. // Otherwise, utilize the filesize @@ -2320,7 +2318,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { if (!responseHeader.empty()) { responseHeader += "\r\n"; } - long object_age = time(NULL) - filectime; + long object_age = time(NULL) - filemodtime; responseHeader += std::string("Age: ") + std::to_string(object_age < 0 ? 0 : object_age); } diff --git a/src/XrdPfc/XrdPfcIOFile.cc b/src/XrdPfc/XrdPfcIOFile.cc index da0dfb1085c..d0b3ebef169 100644 --- a/src/XrdPfc/XrdPfcIOFile.cc +++ b/src/XrdPfc/XrdPfcIOFile.cc @@ -54,12 +54,10 @@ IOFile::~IOFile() //______________________________________________________________________________ int IOFile::Fstat(struct stat &sbuff) { - std::string name = GetFilename() + Info::s_infoExtension; - int res = 0; if( ! m_localStat) { - res = initCachedStat(name.c_str()); + res = initCachedStat(); if (res) return res; } @@ -74,7 +72,7 @@ long long IOFile::FSize() } //______________________________________________________________________________ -int IOFile::initCachedStat(const char* path) +int IOFile::initCachedStat() { // Called indirectly from the constructor. @@ -83,18 +81,24 @@ int IOFile::initCachedStat(const char* path) int res = -1; struct stat tmpStat; - if (m_cache.GetOss()->Stat(GetFilename().c_str(), &tmpStat) == XrdOssOK) + std::string fname = GetFilename(); + std::string iname = fname + Info::s_infoExtension; + if (m_cache.GetOss()->Stat(fname.c_str(), &tmpStat) == XrdOssOK) { XrdOssDF* infoFile = m_cache.GetOss()->newFile(Cache::GetInstance().RefConfiguration().m_username.c_str()); XrdOucEnv myEnv; int res_open; - if ((res_open = infoFile->Open(path, O_RDONLY, 0600, myEnv)) == XrdOssOK) + if ((res_open = infoFile->Open(iname.c_str(), O_RDONLY, 0600, myEnv)) == XrdOssOK) { Info info(m_cache.GetTrace()); - if (info.Read(infoFile, path)) + if (info.Read(infoFile, iname.c_str())) { + // The filesize from the file itself may be misleading if its download is incomplete; take it from the cinfo. tmpStat.st_size = info.GetFileSize(); - TRACEIO(Info, trace_pfx << "successfully read size from info file = " << tmpStat.st_size); + // We are arguably abusing the mtime to be the creation time of the file; then ctime becomes the + // last time additional data was cached. + tmpStat.st_mtime = info.GetCreationTime(); + TRACEIO(Info, trace_pfx << "successfully read size " << tmpStat.st_size << " and creation time " << tmpStat.st_mtime << " from info file"); res = 0; } else diff --git a/src/XrdPfc/XrdPfcIOFile.hh b/src/XrdPfc/XrdPfcIOFile.hh index f8c6fe09ebf..7e771dbbfb2 100644 --- a/src/XrdPfc/XrdPfcIOFile.hh +++ b/src/XrdPfc/XrdPfcIOFile.hh @@ -87,7 +87,7 @@ private: int ReadVEnd(int retval, ReadReqRH *rh); struct stat *m_localStat; - int initCachedStat(const char* path); + int initCachedStat(); }; From 984efbc72bdad86b43923569f4dfa707b7a287a2 Mon Sep 17 00:00:00 2001 From: Chris Green Date: Wed, 22 Feb 2023 08:38:17 -0600 Subject: [PATCH 212/773] Enable scitokens-cpp client plugin under `XRDCL_ONLY` --- cmake/XRootDDefaults.cmake | 2 +- src/CMakeLists.txt | 10 +++++++++- src/XrdSciTokens.cmake | 7 ------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index f9bccf6549d..4ff65737b97 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -29,7 +29,7 @@ option( ENABLE_XRDEC "Enable erasure coding component." option( ENABLE_ASAN "Enable adress sanitizer." FALSE ) option( ENABLE_TSAN "Enable thread sanitizer." FALSE ) option( ENABLE_XRDCLHTTP "Enable xrdcl-http plugin." TRUE ) -cmake_dependent_option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE "NOT XRDCL_ONLY" FALSE ) +option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE ) cmake_dependent_option( ENABLE_MACAROONS "Enable Macaroons plugin." TRUE "NOT XRDCL_ONLY" FALSE ) option( FORCE_ENABLED "Fail build if enabled components cannot be built." FALSE ) cmake_dependent_option( USE_SYSTEM_ISAL "Use isa-l installed in the system" FALSE "ENABLE_XRDEC" FALSE ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index abe7cb2a5ef..c1069ca652b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,6 +46,15 @@ if( BUILD_XRDEC ) add_subdirectory( XrdEc ) endif() +if( BUILD_SCITOKENS ) + find_package( SciTokensCpp REQUIRED ) + include_directories( + ${SCITOKENS_CPP_INCLUDE_DIR} + XrdSciTokens/vendor/picojson + XrdSciTokens/vendor/inih ) + include( XrdSecztn ) +endif() + if( NOT XRDCL_ONLY ) include( XrdServer ) include( XrdDaemons ) @@ -79,7 +88,6 @@ if( NOT XRDCL_ONLY ) if( BUILD_SCITOKENS ) include( XrdSciTokens ) - include( XrdSecztn ) endif() endif() diff --git a/src/XrdSciTokens.cmake b/src/XrdSciTokens.cmake index 5f400461761..ae22085e892 100644 --- a/src/XrdSciTokens.cmake +++ b/src/XrdSciTokens.cmake @@ -1,17 +1,10 @@ include( XRootDCommon ) -find_package( SciTokensCpp REQUIRED ) - #------------------------------------------------------------------------------- # Modules #------------------------------------------------------------------------------- set( LIB_XRD_SCITOKENS XrdAccSciTokens-${PLUGIN_VERSION} ) -include_directories( - ${SCITOKENS_CPP_INCLUDE_DIR} - XrdSciTokens/vendor/picojson - XrdSciTokens/vendor/inih ) - #------------------------------------------------------------------------------- # The XrdPfc library #------------------------------------------------------------------------------- From 0d9f3a35f3e55064a3eae1abe5c5fdbb04c47504 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 18 Feb 2023 12:12:08 -0600 Subject: [PATCH 213/773] Override error message for EAUTH On Linux platforms, the "authentication denied" error is mapped to the EBADE errno (as EAUTH doesn't exist). That results in authentication errors in XrdPss to generate the "invalid exchange" error message (this is because XrdPss takes the errno from XrdPosix and XrdPosix returns EBADE/EAUTH). Rather than start tinkering with alternate errno's for this case, simply map EBADE's error message to "authentication denied". --- src/XrdSys/XrdSysE2T.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/XrdSys/XrdSysE2T.cc b/src/XrdSys/XrdSysE2T.cc index f1f922262c6..9cb3621baa6 100644 --- a/src/XrdSys/XrdSysE2T.cc +++ b/src/XrdSys/XrdSysE2T.cc @@ -63,6 +63,12 @@ int initErrTable() lastGood = i; } } + // NOTE: On systems without EAUTH (authentication error; currently all Glibc systems but GNU Hurd), + // EAUTH is remapped to EBADE ('invalid exchange'). Given there's no current XRootD use of a + // syscall that can return EBADE, we assume EBADE really means authentication denied. +#if defined(EBADE) + Errno2String[EBADE] = "authentication failed - possible invalid exchange"; +#endif // Supply generic message for missing ones // From 7096e2eac803403eafd01f14846240a661f96fac Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 1 Mar 2023 16:46:52 -0800 Subject: [PATCH 214/773] [SSI] Avoid file system+SSI feature interference that caused problems. --- src/XrdCms/XrdCmsFinder.cc | 6 +++++ src/XrdSsi/XrdSsiSfsConfig.cc | 47 +++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/XrdCms/XrdCmsFinder.cc b/src/XrdCms/XrdCmsFinder.cc index 6d3d85cd30b..c9b41bd59dc 100644 --- a/src/XrdCms/XrdCmsFinder.cc +++ b/src/XrdCms/XrdCmsFinder.cc @@ -994,6 +994,12 @@ int XrdCmsFinderTRG::Configure(const char *cfn, char *Ags, XrdOucEnv *envP) // if (XrdSysThread::Run(&tid, StartRsp, (void *)this, 0, "cms i/f")) {Say.Emsg("Config", errno, "start performance monitor."); return 0;} } + +// Record the address of this cms client +// + if (What == XrdCmsClientConfig::configServer) + envP->PutPtr("XrdCmsClientT*", (XrdCmsClient*)this); + // All done // return 1; diff --git a/src/XrdSsi/XrdSsiSfsConfig.cc b/src/XrdSsi/XrdSsiSfsConfig.cc index cfe4435dee2..a2408d022fb 100644 --- a/src/XrdSsi/XrdSsiSfsConfig.cc +++ b/src/XrdSsi/XrdSsiSfsConfig.cc @@ -219,8 +219,14 @@ bool XrdSsiSfsConfig::Configure(const char *cFN, XrdOucEnv *envP) // Configure filesystem callout as needed // - fsChk = FSPath.NotEmpty(); - if (isServer && !theFS) fsChk = false; + if ((fsChk = FSPath.NotEmpty())) + {if (!theFS && !isCms) + {Log.Emsg("Config", "Specifying an fspath requires SSI to be stacked " + "with a file system!"); + return false; + } + if (isServer && !theFS && !isCms) fsChk = false; + } // Perform historical phase 2 initialization // @@ -293,8 +299,10 @@ class XrdOss; int XrdSsiSfsConfig::ConfigCms(XrdOucEnv *envP) { + EPNAME("SsiSfsConfig"); static const int cmsOpt = XrdCms::IsTarget; - XrdCmsClient *cmsP, *(*CmsGC)(XrdSysLogger *, int, int, XrdOss *); + const char *tident = ""; + XrdCmsClient *cmsP = 0, *(*CmsGC)(XrdSysLogger *, int, int, XrdOss *); XrdSysLogger *myLogger = Log.logger(); // Check if we are configuring a simple standalone server @@ -306,18 +314,31 @@ int XrdSsiSfsConfig::ConfigCms(XrdOucEnv *envP) return 0; } +// We now must make sure only one cms client is in effect. +// + if ((cmsP = (XrdCmsClient*)envP->GetPtr("XrdCmsClientT*"))) + {if (CmsLib) Log.Say("Config warning: ignoring cmslib directive; " + "using existing cms instance!"); + SsiCms = new XrdSsiCms(cmsP); + DEBUG("Config: Using cms clientT from environment!"); + return 0; + } + DEBUG("Config: Allocating new cms clientT!"); + // 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); +// Otherwise, simply get the default client. In any case configure them. +// + if (!cmsP) + {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); } - else cmsP = XrdCms::GetDefaultClient(myLogger, cmsOpt, myPort); // If we have a client object onfigure it // From 4961d6d7411d6a8675be8ce8df1d6ea37628b692 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 1 Mar 2023 16:50:01 -0800 Subject: [PATCH 215/773] Update notes on SSI interference fix. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 33572e5648a..25c3cd339f0 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -16,5 +16,7 @@ Prerelease Notes + **Major bug fixes** + **Minor bug fixes** + **[SSI]** Avoid file system+SSI feature interference that caused problems. + **Commit: 7096e2e + **Miscellaneous** From 31d6122520f5f036e046b1cdd5567e410548b02b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sat, 25 Feb 2023 11:27:27 +0100 Subject: [PATCH 216/773] Adjust build rules to not depend on scitokenscpp to build libXrdSecztn This partially reverts commit 984efbc72bdad86b43923569f4dfa707b7a287a2. Actually, libXrdSecztn doesn't depend on scitokens-cpp, so we can always build it even when -DENABLE_SCITOKENS=FALSE. Therefore, also remove the conditional in the spec file to always include the plugin in xrootd-libs. --- cmake/XRootDDefaults.cmake | 2 +- packaging/rhel/xrootd.spec.in | 2 -- src/CMakeLists.txt | 11 +---------- src/XrdSciTokens.cmake | 7 +++++++ 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index 4ff65737b97..f9bccf6549d 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -29,7 +29,7 @@ option( ENABLE_XRDEC "Enable erasure coding component." option( ENABLE_ASAN "Enable adress sanitizer." FALSE ) option( ENABLE_TSAN "Enable thread sanitizer." FALSE ) option( ENABLE_XRDCLHTTP "Enable xrdcl-http plugin." TRUE ) -option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE ) +cmake_dependent_option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE "NOT XRDCL_ONLY" FALSE ) cmake_dependent_option( ENABLE_MACAROONS "Enable Macaroons plugin." TRUE "NOT XRDCL_ONLY" FALSE ) option( FORCE_ENABLED "Fail build if enabled components cannot be built." FALSE ) cmake_dependent_option( USE_SYSTEM_ISAL "Use isa-l installed in the system" FALSE "ENABLE_XRDEC" FALSE ) diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index 3182309f438..d27134ea039 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -883,9 +883,7 @@ fi %{_libdir}/libXrdSecpwd-5.so %{_libdir}/libXrdSecsss-5.so %{_libdir}/libXrdSecunix-5.so -%if %{?_with_scitokens:1}%{!?_with_scitokens:0} %{_libdir}/libXrdSecztn-5.so -%endif %{_libdir}/libXrdUtils.so.3* %{_libdir}/libXrdXml.so.3* diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c1069ca652b..fd1cde70dd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ include( XrdSec ) include( XrdXml ) include( XrdHeaders ) include( XrdSecgsi ) +include( XrdSecztn ) if( BUILD_KRB5 ) include( XrdSeckrb5 ) @@ -46,15 +47,6 @@ if( BUILD_XRDEC ) add_subdirectory( XrdEc ) endif() -if( BUILD_SCITOKENS ) - find_package( SciTokensCpp REQUIRED ) - include_directories( - ${SCITOKENS_CPP_INCLUDE_DIR} - XrdSciTokens/vendor/picojson - XrdSciTokens/vendor/inih ) - include( XrdSecztn ) -endif() - if( NOT XRDCL_ONLY ) include( XrdServer ) include( XrdDaemons ) @@ -62,7 +54,6 @@ if( NOT XRDCL_ONLY ) include( XrdFfs ) include( XrdPlugins ) include( XrdSsi ) - include( XrdPfc ) if( CMAKE_COMPILER_IS_GNUCXX ) diff --git a/src/XrdSciTokens.cmake b/src/XrdSciTokens.cmake index ae22085e892..5f400461761 100644 --- a/src/XrdSciTokens.cmake +++ b/src/XrdSciTokens.cmake @@ -1,10 +1,17 @@ include( XRootDCommon ) +find_package( SciTokensCpp REQUIRED ) + #------------------------------------------------------------------------------- # Modules #------------------------------------------------------------------------------- set( LIB_XRD_SCITOKENS XrdAccSciTokens-${PLUGIN_VERSION} ) +include_directories( + ${SCITOKENS_CPP_INCLUDE_DIR} + XrdSciTokens/vendor/picojson + XrdSciTokens/vendor/inih ) + #------------------------------------------------------------------------------- # The XrdPfc library #------------------------------------------------------------------------------- From bb02458192b1ddb85fa551b5ed3cf8635cca7224 Mon Sep 17 00:00:00 2001 From: Andreas Joachim Peters Date: Tue, 28 Feb 2023 17:27:46 +0100 Subject: [PATCH 217/773] [XrdApps] xrdreplay: fix SEGVs when verifying or printing replays introduced by introduction of buffer-pool for buffer allocation --- src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc b/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc index 2f0e43d84f4..8e16590aa53 100644 --- a/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc +++ b/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc @@ -626,8 +626,6 @@ class ActionExecutor }); else { - for (auto& ch : chunks) - delete[](char*) ch.buffer; ending.reset(); closing.reset(); } @@ -658,8 +656,6 @@ class ActionExecutor }); else { - for (auto& ch : chunks) - delete[](char*) ch.buffer; ending.reset(); closing.reset(); } From 7fe4fd91a9610b0b462245c6d45801e1b3692332 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 6 Mar 2023 15:35:53 +0100 Subject: [PATCH 218/773] [CI] Use actions/checkout@v3 for Alma and Alpine Linux --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a8dca0e634..8288fd22a74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,7 +60,7 @@ jobs: dnf clean all - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build with cmake run: | @@ -151,7 +151,7 @@ jobs: dnf clean all - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build with cmake run: | @@ -233,7 +233,7 @@ jobs: zlib-dev - name: Clone repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build with cmake run: | From 32887880d049910eb9b215f715cc65e7c60c5fc4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 8 Mar 2023 17:01:50 +0100 Subject: [PATCH 219/773] [XrdApps] xrdreplay: clear allocated buffers also on dry runs --- src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc b/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc index 8e16590aa53..98f686698a9 100644 --- a/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc +++ b/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc @@ -626,6 +626,7 @@ class ActionExecutor }); else { + buffers.clear(); ending.reset(); closing.reset(); } @@ -656,6 +657,7 @@ class ActionExecutor }); else { + buffers.clear(); ending.reset(); closing.reset(); } From d1e178be0cd090d8a46b968fabf5da5f16f12b21 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 8 Mar 2023 17:03:36 +0100 Subject: [PATCH 220/773] [XrdApps] xrdreplay: actually lock the mutex when reclaiming memory --- src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc b/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc index 98f686698a9..b5abbd7b386 100644 --- a/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc +++ b/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc @@ -87,7 +87,7 @@ class BufferPool //-------------------------------------------------------------------------- void Reclaim( size_t length ) { - std::unique_lock lck; + std::unique_lock lck(mtx); available += length; cv.notify_all(); } From 78128f9be41b8f562a1181ab3786bcd573136135 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 6 Mar 2023 17:16:58 +0100 Subject: [PATCH 221/773] [Xrd] Wait for the current PollE poll to finish after removing a Link --- src/Xrd/XrdLinkCtl.cc | 3 +- src/Xrd/XrdPollE.hh | 13 +++++- src/Xrd/XrdPollE.icc | 106 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 115 insertions(+), 7 deletions(-) diff --git a/src/Xrd/XrdLinkCtl.cc b/src/Xrd/XrdLinkCtl.cc index 30f8847b0b2..351c28561c7 100644 --- a/src/Xrd/XrdLinkCtl.cc +++ b/src/Xrd/XrdLinkCtl.cc @@ -117,7 +117,8 @@ XrdLink *XrdLinkCtl::Alloc(XrdNetAddr &peer, int opts) LTMutex.Lock(); if (LinkBat[peerFD]) {LTMutex.UnLock(); - Log.Emsg("Link", "attempt to reuse active link"); + snprintf(hName, sizeof(hName), "%d", peerFD); + Log.Emsg("Link", "attempt to reuse active link FD -",hName); return (XrdLink *)0; } diff --git a/src/Xrd/XrdPollE.hh b/src/Xrd/XrdPollE.hh index 4c3b7497c97..981ec4c7f2f 100644 --- a/src/Xrd/XrdPollE.hh +++ b/src/Xrd/XrdPollE.hh @@ -47,8 +47,12 @@ public: void Start(XrdSysSemaphore *syncp, int &rc); - XrdPollE(struct epoll_event *ptab, int numfd, int pfd) - {PollTab = ptab; PollMax = numfd; PollDfd = pfd;} + XrdPollE(struct epoll_event *ptab, int numfd, int pfd, int wfd) + : WaitFdSem(0) + {PollTab = ptab; PollMax = numfd; PollDfd = pfd; + WaitFd = wfd; + } + ~XrdPollE(); protected: @@ -57,7 +61,10 @@ protected: const char *x2Text(unsigned int evf, char *buff); private: +int AddWaitFd(); +void HandleWaitFd(const unsigned int events); void remFD(XrdPollInfo &pInfo, unsigned int events); +void Wait4Poller(); #ifdef EPOLLONESHOT static const int ePollOneShot = EPOLLONESHOT; @@ -70,5 +77,7 @@ void remFD(XrdPollInfo &pInfo, unsigned int events); struct epoll_event *PollTab; int PollDfd; int PollMax; + int WaitFd; +XrdSysSemaphore WaitFdSem; }; #endif diff --git a/src/Xrd/XrdPollE.icc b/src/Xrd/XrdPollE.icc index ca729137c99..e6b11d24e5e 100644 --- a/src/Xrd/XrdPollE.icc +++ b/src/Xrd/XrdPollE.icc @@ -33,6 +33,7 @@ #include #include #include +#include #include "Xrd/XrdPollE.hh" #include "Xrd/XrdScheduler.hh" @@ -43,7 +44,7 @@ XrdPoll *XrdPoll::newPoller(int pollid, int maxfd) { - int pfd, bytes, alignment, pagsz = getpagesize(); + int pfd, wfd, bytes, alignment, pagsz = getpagesize(); struct epoll_event *pp; // Open the /dev/poll driver @@ -56,12 +57,20 @@ XrdPoll *XrdPoll::newPoller(int pollid, int maxfd) #endif {Log.Emsg("Poll", errno, "create epoll device"); return 0;} + if ((wfd = eventfd(0, EFD_CLOEXEC)) < 0) + {Log.Emsg("Poll", errno, + "create an eventfd as the wait-poller descriptor"); + close(pfd); + return 0; + } + // Calculate the size of the poll table and allocate it // bytes = maxfd * sizeof(struct epoll_event); alignment = (bytes < pagsz ? 1024 : pagsz); if (posix_memalign((void **)&pp, alignment, bytes)) {Log.Emsg("Poll", ENOMEM, "create poll table"); + close(wfd); close(pfd); return 0; } @@ -69,7 +78,7 @@ XrdPoll *XrdPoll::newPoller(int pollid, int maxfd) // Create new poll object // memset((void *)pp, 0, bytes); - return (XrdPoll *)new XrdPollE(pp, maxfd, pfd); + return (XrdPoll *)new XrdPollE(pp, maxfd, pfd, wfd); } /******************************************************************************/ @@ -79,9 +88,30 @@ XrdPoll *XrdPoll::newPoller(int pollid, int maxfd) XrdPollE::~XrdPollE() { if (PollTab) free(PollTab); + if (WaitFd >= 0) close(WaitFd); if (PollDfd >= 0) close(PollDfd); } +/******************************************************************************/ +/* A d d W a i t F d */ +/******************************************************************************/ + +int XrdPollE::AddWaitFd() +{ + const unsigned int myPollEvts = EPOLLIN; + struct epoll_event myEvent = {myPollEvts, {(void *)&WaitFd}}; + +// Add the waitfd to the poll set +// + if (epoll_ctl(PollDfd, EPOLL_CTL_ADD, WaitFd, &myEvent) < 0) + {int rc = errno; + Log.Emsg("Poll", rc, "include the wait FD in the poll set"); + return rc; + } + + return 0; +} + /******************************************************************************/ /* D i s a b l e */ /******************************************************************************/ @@ -164,6 +194,44 @@ void XrdPollE::Exclude(XrdPollInfo &pInfo) // Forcibly remove the link from the poll set // remFD(pInfo, 0); + +// Wait to make sure the poll thread has completed handling the last set of +// events which may have included this Link. The handing includes examining the +// Link's PollInfo before deciding if to schedule the Link. After we return, +// the PollInfo may be reset and the Link could be subsequently reused. +// + Wait4Poller(); +} + +/******************************************************************************/ +/* H a n d l e W a i t F d */ +/******************************************************************************/ + +void XrdPollE::HandleWaitFd(const unsigned int events) +{ +// Used by the polling thread to signal waiters that a polling loop has been +// completed. +// + eventfd_t ic, cnt; + +// We don't expect anything but EPOLLIN. But if we get an error (errors +// can be reported, despite not being selected events) abort rather than +// potentially keeping the poll thread busy repeatedly looping. +// + if (!(events & EPOLLIN) || (events & (EPOLLERR | EPOLLHUP))) + {char eBuff[64]; + Log.Emsg("Poll", "wait-poller handler:", x2Text(events, eBuff)); + if (events & (EPOLLERR | EPOLLHUP)) + abort(); + return; + } + + if (eventfd_read(WaitFd, &cnt) < 0) + {Log.Emsg("Poll", errno, "read from the wait-poller descriptor"); + return; + } + + for (ic=0;icPost(); + return; + } + // Indicate to the starting thread that all went well // retcode = 0; @@ -247,8 +323,11 @@ void XrdPollE::Start(XrdSysSemaphore *syncsem, int &retcode) // Checkout which links must be dispatched (no need to lock) // jfirst = jlast = 0; num2sched = 0; + haveWaiters = false; waitFdEvents = 0; for (i = 0; i < numpolled; i++) - {if ((pInfo = (XrdPollInfo *)PollTab[i].data.ptr)) + {if (PollTab[i].data.ptr == &WaitFd) + {haveWaiters = true; waitFdEvents = PollTab[i].events;} + else if ((pInfo = (XrdPollInfo *)PollTab[i].data.ptr)) {if (!(pInfo->isEnabled) && pInfo->FD >= 0) remFD(*pInfo, PollTab[i].events); else {pInfo->isEnabled = 0; @@ -272,9 +351,28 @@ void XrdPollE::Start(XrdSysSemaphore *syncsem, int &retcode) // if (num2sched == 1) Sched.Schedule(jfirst); else if (num2sched) Sched.Schedule(num2sched, jfirst, jlast); + + if (haveWaiters) HandleWaitFd(waitFdEvents); } while(1); } +/******************************************************************************/ +/* W a i t 4 P o l l e r */ +/******************************************************************************/ + +void XrdPollE::Wait4Poller() +{ +// Makes the caller wait for the polling thread to complete an event processing +// loop. +// + if (eventfd_write(WaitFd, 1) < 0) + {Log.Emsg("Poll", errno, "write to the wait-poller descriptor"); + return; + } + + WaitFdSem.Wait(); +} + /******************************************************************************/ /* x 2 T e x t */ /******************************************************************************/ From 33ce31ff5d09ded2fd74b38acf84f5393c117628 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 7 Mar 2023 11:38:02 +0100 Subject: [PATCH 222/773] [XrdCl] Do not reuse sessionId when a new Stream object is made --- src/XrdCl/XrdClStream.cc | 7 ++++++- src/XrdCl/XrdClStream.hh | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClStream.cc b/src/XrdCl/XrdClStream.cc index 4d8324af31e..cb64cbb6bc6 100644 --- a/src/XrdCl/XrdClStream.cc +++ b/src/XrdCl/XrdClStream.cc @@ -44,6 +44,11 @@ namespace XrdCl { + //---------------------------------------------------------------------------- + // Statics + //---------------------------------------------------------------------------- + RAtomic_uint64_t Stream::sSessCntGen{0}; + //---------------------------------------------------------------------------- // Incoming message helper //---------------------------------------------------------------------------- @@ -615,7 +620,7 @@ namespace XrdCl pLastFatalError = XRootDStatus(); pConnectionCount = 0; uint16_t numSub = pTransport->SubStreamNumber( *pChannelData ); - ++pSessionId; + pSessionId = ++sSessCntGen; //------------------------------------------------------------------------ // Create the streams if they don't exist yet diff --git a/src/XrdCl/XrdClStream.hh b/src/XrdCl/XrdClStream.hh index 5aac742dc78..19522d37de9 100644 --- a/src/XrdCl/XrdClStream.hh +++ b/src/XrdCl/XrdClStream.hh @@ -29,6 +29,7 @@ #include "XrdCl/XrdClUtils.hh" #include "XrdSys/XrdSysPthread.hh" +#include "XrdSys/XrdSysRAtomic.hh" #include "XrdNet/XrdNetAddr.hh" #include #include @@ -371,6 +372,11 @@ namespace XrdCl // Data stream on-connect handler //------------------------------------------------------------------------ std::shared_ptr pOnDataConnJob; + + //------------------------------------------------------------------------ + // Track last assigned Id across all Streams, to ensure unique sessionId + //------------------------------------------------------------------------ + static RAtomic_uint64_t sSessCntGen; }; } From 4a174afc8d6bac94a2a1296031d5d7fbdacea9c2 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 9 Mar 2023 14:43:32 +0100 Subject: [PATCH 223/773] [XrdClTests] Stop some intermittent failures of XrdClTests/SocketTest: By ensuring repeated short-reads() correctly decrease the number of remaining bytes wanted by the number already read --- tests/XrdClTests/SocketTest.cc | 32 +++++++---------------- tests/common/Utils.cc | 47 ++++++++++++++++++++++++++++++++++ tests/common/Utils.hh | 10 ++++++++ 3 files changed, 66 insertions(+), 23 deletions(-) diff --git a/tests/XrdClTests/SocketTest.cc b/tests/XrdClTests/SocketTest.cc index 583577daece..e6a4516aafd 100644 --- a/tests/XrdClTests/SocketTest.cc +++ b/tests/XrdClTests/SocketTest.cc @@ -131,7 +131,7 @@ class RandomHandler: public ClientHandler char buffer[50000]; log->Debug( 1, "Sending %d packets to the client", packets ); - if( ::write( socket, &packets, 1 ) != 1 ) + if( ::Utils::Write( socket, &packets, 1 ) != 1 ) { log->Error( 1, "Unable to send the packet count" ); return; @@ -147,12 +147,12 @@ class RandomHandler: public ClientHandler return; } - if( ::write( socket, &packetSize, 2 ) != 2 ) + if( ::Utils::Write( socket, &packetSize, 2 ) != 2 ) { log->Error( 1, "Unable to send the packet size" ); return; } - if( ::write( socket, buffer, packetSize ) != packetSize ) + if( ::Utils::Write( socket, buffer, packetSize ) != packetSize ) { log->Error( 1, "Unable to send the %d bytes of random data", packetSize ); @@ -164,10 +164,7 @@ class RandomHandler: public ClientHandler //------------------------------------------------------------------------ // Receive some data //------------------------------------------------------------------------ - ssize_t totalRead; - char *current; - - if( ::read( socket, &packets, 1 ) != 1 ) + if( ::Utils::Read( socket, &packets, 1 ) != 1 ) { log->Error( 1, "Unable to receive the packet count" ); return; @@ -177,28 +174,17 @@ class RandomHandler: public ClientHandler for( int i = 0; i < packets; ++i ) { - totalRead = 0; - current = buffer; - if( ::read( socket, &packetSize, 2 ) != 2 ) + if( ::Utils::Read( socket, &packetSize, 2 ) != 2 ) { log->Error( 1, "Unable to receive the packet size" ); return; } - while(1) + if ( ::Utils::Read( socket, buffer, packetSize ) != packetSize ) { - 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; + 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 ); diff --git a/tests/common/Utils.cc b/tests/common/Utils.cc index cb6447405a6..e029597801a 100644 --- a/tests/common/Utils.cc +++ b/tests/common/Utils.cc @@ -20,6 +20,7 @@ #include "XrdCl/XrdClUtils.hh" #include #include +#include #include #include #include @@ -66,4 +67,50 @@ ssize_t Utils::GetRandomBytes( char *buffer, size_t size ) return size-toRead;; } +//------------------------------------------------------------------------------ +// Write buffer to a socket +//------------------------------------------------------------------------------ +ssize_t Utils::Write( int fd, const void *buf, size_t count ) +{ + ssize_t ret; + size_t remain = count; + const uint8_t *p = (uint8_t*)buf; + while( remain ) + { + do + { + ret = ::write( fd, p, remain ); + } while( ret<0 && errno == EINTR ); + if( ret <= 0 ) + return ret; + p += ret; + remain -= ret; + } + return count; +} + +//------------------------------------------------------------------------------ +// Read buffer from a socket +//------------------------------------------------------------------------------ +ssize_t Utils::Read( int fd, void *buf, size_t count ) +{ + ssize_t ret; + size_t remain = count, totRead = 0; + uint8_t *p = (uint8_t*)buf; + while( remain ) + { + do + { + ret = ::read( fd, p, remain ); + } while( ret<0 && errno == EINTR ); + if( ret == 0 ) + break; + if( ret < 0 ) + return ret; + p += ret; + remain -= ret; + totRead += ret; + } + return totRead; +} } diff --git a/tests/common/Utils.hh b/tests/common/Utils.hh index ecdb8f6d31a..2cb48f9b604 100644 --- a/tests/common/Utils.hh +++ b/tests/common/Utils.hh @@ -92,6 +92,16 @@ class Utils { return crc32_combine( crc1, crc2, len2 ); } + + //-------------------------------------------------------------------------- + //! Read a buffer from a socket + //-------------------------------------------------------------------------- + static ssize_t Read( int fd, void *buf, size_t count ); + + //-------------------------------------------------------------------------- + //! Write a buffer to a socket + //-------------------------------------------------------------------------- + static ssize_t Write( int fd, const void *buf, size_t count ); }; }; From 149da8ab2c6d2962d18aaa095fcd01870b89c71a Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 13 Mar 2023 11:07:46 +0100 Subject: [PATCH 224/773] [XrdTls] Reset socket error condition flag during Init --- src/XrdTls/XrdTlsSocket.cc | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/XrdTls/XrdTlsSocket.cc b/src/XrdTls/XrdTlsSocket.cc index f4b36a91be6..ffdbff166a0 100644 --- a/src/XrdTls/XrdTlsSocket.cc +++ b/src/XrdTls/XrdTlsSocket.cc @@ -505,6 +505,11 @@ const char *XrdTlsSocket::Init( XrdTlsContext &ctx, int sfd, if (hsm) pImpl->hsNoBlock = false; else pImpl->hsNoBlock = true; +// Reset the handshake and fatal error indicators +// + pImpl->hsDone = false; + pImpl->fatal = 0; + // The glories of OpenSSL require that we do some fancy footwork with the // handshake timeout. If there is one and this is a server and the server // wants blocking reads, we initially set the socket as non-blocking as the @@ -536,6 +541,7 @@ const char *XrdTlsSocket::Init( XrdTlsContext &ctx, int sfd, XrdTls::RC XrdTlsSocket::Peek( char *buffer, size_t size, int &bytesPeek ) { + EPNAME("Peek"); XrdSysMutexHelper mHelper; int ssler; @@ -550,7 +556,10 @@ XrdTls::RC XrdTlsSocket::Peek( char *buffer, size_t size, int &bytesPeek ) // SEGV when called after such an error. //------------------------------------------------------------------------ - if (pImpl->fatal) return (XrdTls::RC)pImpl->fatal; + if (pImpl->fatal) + {DBG_SIO("Failing due to previous error, fatal=" << (int)pImpl->fatal); + return (XrdTls::RC)pImpl->fatal; + } //------------------------------------------------------------------------ // If necessary, SSL_read() will negotiate a TLS/SSL session, so we don't @@ -640,7 +649,10 @@ XrdTls::RC XrdTlsSocket::Read( char *buffer, size_t size, int &bytesRead ) // SEGV when called after such an error. //------------------------------------------------------------------------ - if (pImpl->fatal) return (XrdTls::RC)pImpl->fatal; + if (pImpl->fatal) + {DBG_SIO("Failing due to previous error, fatal=" << (int)pImpl->fatal); + return (XrdTls::RC)pImpl->fatal; + } //------------------------------------------------------------------------ // If necessary, SSL_read() will negotiate a TLS/SSL session, so we don't @@ -785,7 +797,10 @@ XrdTls::RC XrdTlsSocket::Write( const char *buffer, size_t size, // SEGV when called after such an error. //------------------------------------------------------------------------ - if (pImpl->fatal) return (XrdTls::RC)pImpl->fatal; + if (pImpl->fatal) + {DBG_SIO("Failing due to previous error, fatal=" << (int)pImpl->fatal); + return (XrdTls::RC)pImpl->fatal; + } //------------------------------------------------------------------------ // If necessary, SSL_write() will negotiate a TLS/SSL session, so we don't From 10904154c6c18866fc6dd0e996de8c818b7e2f98 Mon Sep 17 00:00:00 2001 From: Radu Carpa Date: Thu, 16 Mar 2023 08:52:11 +0100 Subject: [PATCH 225/773] limit max number of sockets in poller. Fix #1962 The number of FD in the poller is automatically configured from the value of `ulimit -n`. On newer linux installations, this limit is much higher by default. This can result in unbound memory consumption. There is already a mechanism in place to handle the case when `ulimit -n == unlimited`. Extend it to support any unreasonable big limit value. --- 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 f1d13566509..f838f214046 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -1187,7 +1187,7 @@ int XrdConfig::setFDL() // Set the limit to the maximum allowed // - if (rlim.rlim_max == RLIM_INFINITY) rlim.rlim_cur = maxFD; + if (rlim.rlim_max == RLIM_INFINITY || 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; From 321810185d7e6ec2bccc4b5345283687d5d9add8 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 13 Mar 2023 10:24:36 +0100 Subject: [PATCH 226/773] [XrdCrypto] Fix utility function XrdCryptosslASN1toUTC to return in UTC --- src/XrdCrypto/XrdCryptoAux.cc | 1 + src/XrdCrypto/XrdCryptoX509.cc | 6 +----- src/XrdCrypto/XrdCryptosslAux.cc | 8 ++++---- src/XrdCrypto/XrdCryptosslgsiAux.cc | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/XrdCrypto/XrdCryptoAux.cc b/src/XrdCrypto/XrdCryptoAux.cc index 709ae319d6d..4aa48746d19 100644 --- a/src/XrdCrypto/XrdCryptoAux.cc +++ b/src/XrdCrypto/XrdCryptoAux.cc @@ -77,6 +77,7 @@ void XrdCryptoSetTrace(kXR_int32 trace) time_t XrdCryptoTZCorr() { // Time Zone correction (wrt UTC) + // Assumes no DST, the correction is not expected to change during the year if (!TZInitialized) { time_t now = time(0); diff --git a/src/XrdCrypto/XrdCryptoX509.cc b/src/XrdCrypto/XrdCryptoX509.cc index 8ec8fd84733..8aa520366bd 100644 --- a/src/XrdCrypto/XrdCryptoX509.cc +++ b/src/XrdCrypto/XrdCryptoX509.cc @@ -104,12 +104,10 @@ int XrdCryptoX509::BitStrength() //_____________________________________________________________________________ bool XrdCryptoX509::IsValid(int when) { - // Check validity at local time 'when'. Use when =0 (default) to check + // Check validity at UTC time 'when'. Use when =0 (default) to check // at present time. int now = (when > 0) ? when : (int)time(0); - // Correct for time zone (certificate times are UTC plus, eventually, DST - now -= XrdCryptoTZCorr(); return (now >= (NotBefore()-kAllowedSkew) && now <= NotAfter()); } @@ -120,8 +118,6 @@ bool XrdCryptoX509::IsExpired(int when) // at present time. int now = (when > 0) ? when : (int)time(0); - // Correct for time zone (certificate times are UTC plus, eventually, DST - now -= XrdCryptoTZCorr(); return (now > NotAfter()); } diff --git a/src/XrdCrypto/XrdCryptosslAux.cc b/src/XrdCrypto/XrdCryptosslAux.cc index eebcd181f77..161dc2399bd 100644 --- a/src/XrdCrypto/XrdCryptosslAux.cc +++ b/src/XrdCrypto/XrdCryptosslAux.cc @@ -735,7 +735,7 @@ time_t XrdCryptosslASN1toUTC(const ASN1_TIME *tsn1) // Init also the ones not used by mktime ltm.tm_wday = 0; // day of the week ltm.tm_yday = 0; // day in the year - ltm.tm_isdst = -1; // daylight saving time + ltm.tm_isdst = 0; // we will correct with an offset without dst // // Renormalize some values: year should be modulo 1900 if (ltm.tm_year < 90) @@ -744,10 +744,10 @@ time_t XrdCryptosslASN1toUTC(const ASN1_TIME *tsn1) // month should in [0, 11] (ltm.tm_mon)--; // - // Calculate UTC + // Calculate as if the UTC stamp was a localtime with no dst etime = mktime(<m); - // Include DST shift; here, because we have the information - if (ltm.tm_isdst > 0) etime += XrdCryptoDSTShift; + // Correct to UTC + etime += XrdCryptoTZCorr(); // Notify, if requested // DEBUG(" UTC: "<NotAfter() - (int)time(0) + XrdCryptoTZCorr(); + int timeleft = xcpi->NotAfter() - (int)time(0); if (timeleft < 0) { PRINT("EEC certificate has expired"); return -kErrPX_ExpiredEEC; From 96bdb1de36b8785cd2e6abb25eb189ff8f0a553e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 22 Feb 2023 08:27:15 +0100 Subject: [PATCH 227/773] [CMake] Use GNUInstallDirs.cmake distributed by CMake Since we now require CMake 3.16 to build, the standard module provided by CMake should be sufficient. --- cmake/GNUInstallDirs.cmake | 182 ------------------------------------- 1 file changed, 182 deletions(-) delete mode 100644 cmake/GNUInstallDirs.cmake diff --git a/cmake/GNUInstallDirs.cmake b/cmake/GNUInstallDirs.cmake deleted file mode 100644 index a114dcb2e18..00000000000 --- a/cmake/GNUInstallDirs.cmake +++ /dev/null @@ -1,182 +0,0 @@ -# - Define GNU standard installation directories -# Provides install directory variables as defined for GNU software: -# http://www.gnu.org/prep/standards/html_node/Directory-Variables.html -# Inclusion of this module defines the following variables: -# CMAKE_INSTALL_

- destination for files of a given type -# CMAKE_INSTALL_FULL_ - corresponding absolute path -# where is one of: -# BINDIR - user executables (bin) -# SBINDIR - system admin executables (sbin) -# LIBEXECDIR - program executables (libexec) -# SYSCONFDIR - read-only single-machine data (etc) -# SHAREDSTATEDIR - modifiable architecture-independent data (com) -# LOCALSTATEDIR - modifiable single-machine data (var) -# LIBDIR - object code libraries (lib or lib64) -# INCLUDEDIR - C header files (include) -# OLDINCLUDEDIR - C header files for non-gcc (/usr/include) -# DATAROOTDIR - read-only architecture-independent data root (share) -# DATADIR - read-only architecture-independent data (DATAROOTDIR) -# INFODIR - info documentation (DATAROOTDIR/info) -# LOCALEDIR - locale-dependent data (DATAROOTDIR/locale) -# MANDIR - man documentation (DATAROOTDIR/man) -# DOCDIR - documentation root (DATAROOTDIR/doc/PROJECT_NAME) -# Each CMAKE_INSTALL_ value may be passed to the DESTINATION options of -# install() commands for the corresponding file type. If the includer does -# not define a value the above-shown default will be used and the value will -# appear in the cache for editing by the user. -# Each CMAKE_INSTALL_FULL_ value contains an absolute path constructed -# from the corresponding destination by prepending (if necessary) the value -# of CMAKE_INSTALL_PREFIX. - -#============================================================================= -# Copyright 2011 Nikita Krupen'ko -# Copyright 2011 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -# Installation directories -# -if(NOT DEFINED CMAKE_INSTALL_BINDIR) - set(CMAKE_INSTALL_BINDIR "bin" CACHE PATH "user executables (bin)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SBINDIR) - set(CMAKE_INSTALL_SBINDIR "sbin" CACHE PATH "system admin executables (sbin)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LIBEXECDIR) - set(CMAKE_INSTALL_LIBEXECDIR "libexec" CACHE PATH "program executables (libexec)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SYSCONFDIR) - set(CMAKE_INSTALL_SYSCONFDIR "etc" CACHE PATH "read-only single-machine data (etc)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SHAREDSTATEDIR) - set(CMAKE_INSTALL_SHAREDSTATEDIR "com" CACHE PATH "modifiable architecture-independent data (com)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LOCALSTATEDIR) - set(CMAKE_INSTALL_LOCALSTATEDIR "var" CACHE PATH "modifiable single-machine data (var)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LIBDIR) - set(_LIBDIR_DEFAULT "lib") - # Override this default 'lib' with 'lib64' iff: - # - we are on Linux system but NOT cross-compiling - # - we are NOT on debian - # - we are on a 64 bits system - # reason is: amd64 ABI: http://www.x86-64.org/documentation/abi.pdf - # Note that the future of multi-arch handling may be even - # more complicated than that: http://wiki.debian.org/Multiarch - if(CMAKE_SYSTEM_NAME MATCHES "Linux" - AND NOT CMAKE_CROSSCOMPILING - AND NOT EXISTS "/etc/debian_version") - if(NOT DEFINED CMAKE_SIZEOF_VOID_P) - message(AUTHOR_WARNING - "Unable to determine default CMAKE_INSTALL_LIBDIR directory because no target architecture is known. " - "Please enable at least one language before including GNUInstallDirs.") - else() - if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(_LIBDIR_DEFAULT "lib64") - endif() - endif() - endif() - set(CMAKE_INSTALL_LIBDIR "${_LIBDIR_DEFAULT}" CACHE PATH "object code libraries (${_LIBDIR_DEFAULT})") -endif() - -if(NOT DEFINED CMAKE_INSTALL_INCLUDEDIR) - set(CMAKE_INSTALL_INCLUDEDIR "include" CACHE PATH "C header files (include)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_OLDINCLUDEDIR) - set(CMAKE_INSTALL_OLDINCLUDEDIR "/usr/include" CACHE PATH "C header files for non-gcc (/usr/include)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_DATAROOTDIR) - set(CMAKE_INSTALL_DATAROOTDIR "share" CACHE PATH "read-only architecture-independent data root (share)") -endif() - -#----------------------------------------------------------------------------- -# Values whose defaults are relative to DATAROOTDIR. Store empty values in -# the cache and store the defaults in local variables if the cache values are -# not set explicitly. This auto-updates the defaults as DATAROOTDIR changes. - -if(NOT CMAKE_INSTALL_DATADIR) - set(CMAKE_INSTALL_DATADIR "" CACHE PATH "read-only architecture-independent data (DATAROOTDIR)") - set(CMAKE_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}") -endif() - -if(NOT CMAKE_INSTALL_INFODIR) - set(CMAKE_INSTALL_INFODIR "" CACHE PATH "info documentation (DATAROOTDIR/info)") - set(CMAKE_INSTALL_INFODIR "${CMAKE_INSTALL_DATAROOTDIR}/info") -endif() - -if(NOT CMAKE_INSTALL_LOCALEDIR) - set(CMAKE_INSTALL_LOCALEDIR "" CACHE PATH "locale-dependent data (DATAROOTDIR/locale)") - set(CMAKE_INSTALL_LOCALEDIR "${CMAKE_INSTALL_DATAROOTDIR}/locale") -endif() - -if(NOT CMAKE_INSTALL_MANDIR) - set(CMAKE_INSTALL_MANDIR "" CACHE PATH "man documentation (DATAROOTDIR/man)") - set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_DATAROOTDIR}/man") -endif() - -if(NOT CMAKE_INSTALL_DOCDIR) - set(CMAKE_INSTALL_DOCDIR "" CACHE PATH "documentation root (DATAROOTDIR/doc/PROJECT_NAME)") - set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}") -endif() - -#----------------------------------------------------------------------------- - -mark_as_advanced( - CMAKE_INSTALL_BINDIR - CMAKE_INSTALL_SBINDIR - CMAKE_INSTALL_LIBEXECDIR - CMAKE_INSTALL_SYSCONFDIR - CMAKE_INSTALL_SHAREDSTATEDIR - CMAKE_INSTALL_LOCALSTATEDIR - CMAKE_INSTALL_LIBDIR - CMAKE_INSTALL_INCLUDEDIR - CMAKE_INSTALL_OLDINCLUDEDIR - CMAKE_INSTALL_DATAROOTDIR - CMAKE_INSTALL_DATADIR - CMAKE_INSTALL_INFODIR - CMAKE_INSTALL_LOCALEDIR - CMAKE_INSTALL_MANDIR - CMAKE_INSTALL_DOCDIR - ) - -# Result directories -# -foreach(dir - BINDIR - SBINDIR - LIBEXECDIR - SYSCONFDIR - SHAREDSTATEDIR - LOCALSTATEDIR - LIBDIR - INCLUDEDIR - OLDINCLUDEDIR - DATAROOTDIR - DATADIR - INFODIR - LOCALEDIR - MANDIR - DOCDIR - ) - if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_${dir}}) - set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_${dir}}") - else() - set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_${dir}}") - endif() -endforeach() From 5af095fc308fd1c83dbb8db6346ef497d66256b0 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 22 Feb 2023 15:44:35 +0100 Subject: [PATCH 228/773] [CMake] Update Findlibuuid.cmake to provide imported target for uuid --- cmake/Findlibuuid.cmake | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmake/Findlibuuid.cmake b/cmake/Findlibuuid.cmake index f18268e5e40..7f07c4b53e0 100644 --- a/cmake/Findlibuuid.cmake +++ b/cmake/Findlibuuid.cmake @@ -4,6 +4,14 @@ # # Find libuuid, DCE compatible Universally Unique Identifier library. # +# Imported Targets +# ^^^^^^^^^^^^^^^^ +# +# This module defines :prop_tgt:`IMPORTED` target: +# +# ``uuid::uuid`` +# The libuuid library, if found. +# # Result Variables # ^^^^^^^^^^^^^^^^ # @@ -52,5 +60,11 @@ unset(CMAKE_REQUIRED_INCLUDES) unset(_uuid_header_only) unset(_have_libuuid) +if(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) mark_as_advanced(UUID_INCLUDE_DIR UUID_LIBRARY) From 5185b689553b8e2f1189ed0394aaff63cdf24c15 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 23 Feb 2023 09:18:26 +0100 Subject: [PATCH 229/773] [CMake] Use CMake imported target for libuuid --- src/XrdCl/CMakeLists.txt | 2 +- src/XrdMacaroons.cmake | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index 32d3ccb4e14..a2b9ee9f79e 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -111,8 +111,8 @@ target_link_libraries( XrdCl XrdXml XrdUtils + uuid::uuid ${CMAKE_THREAD_LIBS_INIT} - ${UUID_LIBRARY} ${ZLIB_LIBRARIES} ${EXTRA_LIBS} ${CMAKE_DL_LIBS} diff --git a/src/XrdMacaroons.cmake b/src/XrdMacaroons.cmake index 5d7fa608466..148ba914481 100644 --- a/src/XrdMacaroons.cmake +++ b/src/XrdMacaroons.cmake @@ -10,7 +10,7 @@ set( LIB_XRD_MACAROONS XrdMacaroons-${PLUGIN_VERSION} ) #------------------------------------------------------------------------------- if( BUILD_MACAROONS ) - include_directories(${MACAROONS_INCLUDES} ${JSON_INCLUDE_DIRS} ${UUID_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR}) + include_directories(${MACAROONS_INCLUDES} ${JSON_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR}) add_library( ${LIB_XRD_MACAROONS} @@ -25,10 +25,10 @@ if( BUILD_MACAROONS ) XrdHttpUtils XrdUtils XrdServer + uuid::uuid ${MACAROONS_LIB} ${JSON_LIBRARIES} ${XROOTD_HTTP_LIB} - ${UUID_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARY}) if( MacOSX ) From f3444897adcf883bf38ef5793accfc5a5ea3c922 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 23 Feb 2023 09:22:51 +0100 Subject: [PATCH 230/773] [CMake] Use CMake imported target for zlib --- src/XrdApps.cmake | 4 ++-- src/XrdCl/CMakeLists.txt | 2 +- src/XrdPlugins.cmake | 2 +- src/XrdSsi.cmake | 2 +- tests/XrdCephTests/CMakeLists.txt | 2 +- tests/XrdClTests/CMakeLists.txt | 2 +- tests/XrdSsiTests/CMakeLists.txt | 2 +- tests/common/CMakeLists.txt | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/XrdApps.cmake b/src/XrdApps.cmake index 90cf0cedf2f..b12138f0d20 100644 --- a/src/XrdApps.cmake +++ b/src/XrdApps.cmake @@ -29,8 +29,8 @@ if( NOT XRDCL_ONLY ) xrdadler32 XrdPosix XrdUtils - ${CMAKE_THREAD_LIBS_INIT} - ${ZLIB_LIBRARIES} ) + ZLIB::ZLIB + ${CMAKE_THREAD_LIBS_INIT}) #----------------------------------------------------------------------------- # xrdcks diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index a2b9ee9f79e..41c59af762f 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -112,8 +112,8 @@ target_link_libraries( XrdXml XrdUtils uuid::uuid + ZLIB::ZLIB ${CMAKE_THREAD_LIBS_INIT} - ${ZLIB_LIBRARIES} ${EXTRA_LIBS} ${CMAKE_DL_LIBS} ${OPENSSL_LIBRARIES}) diff --git a/src/XrdPlugins.cmake b/src/XrdPlugins.cmake index b297e918f4a..a057079a578 100644 --- a/src/XrdPlugins.cmake +++ b/src/XrdPlugins.cmake @@ -136,7 +136,7 @@ add_library( target_link_libraries( ${LIB_XRD_ZCRC32} XrdUtils - ${ZLIB_LIBRARIES} ) + ZLIB::ZLIB) set_target_properties( ${LIB_XRD_ZCRC32} diff --git a/src/XrdSsi.cmake b/src/XrdSsi.cmake index 3511a4633c9..da7f36c94fc 100644 --- a/src/XrdSsi.cmake +++ b/src/XrdSsi.cmake @@ -74,7 +74,7 @@ XrdSsi/XrdSsiShMat.cc XrdSsi/XrdSsiShMat.hh) target_link_libraries( XrdSsiShMap XrdUtils - ${ZLIB_LIBRARIES} + ZLIB::ZLIB ${CMAKE_THREAD_LIBS_INIT} ) set_target_properties( diff --git a/tests/XrdCephTests/CMakeLists.txt b/tests/XrdCephTests/CMakeLists.txt index eb966c2dcb3..383a6fa70f5 100644 --- a/tests/XrdCephTests/CMakeLists.txt +++ b/tests/XrdCephTests/CMakeLists.txt @@ -10,7 +10,7 @@ target_link_libraries( XrdCephTests ${CMAKE_THREAD_LIBS_INIT} ${CPPUNIT_LIBRARIES} - ${ZLIB_LIBRARIES} + ZLIB::ZLIB XrdCephPosix ) #------------------------------------------------------------------------------- diff --git a/tests/XrdClTests/CMakeLists.txt b/tests/XrdClTests/CMakeLists.txt index 0c0e7817359..3206a39d0ce 100644 --- a/tests/XrdClTests/CMakeLists.txt +++ b/tests/XrdClTests/CMakeLists.txt @@ -32,7 +32,7 @@ target_link_libraries( XrdClTestsHelper ${CMAKE_THREAD_LIBS_INIT} ${CPPUNIT_LIBRARIES} - ${ZLIB_LIBRARIES} + ZLIB::ZLIB XrdCl ) add_library( diff --git a/tests/XrdSsiTests/CMakeLists.txt b/tests/XrdSsiTests/CMakeLists.txt index 1a33668c53e..3bbf4d40a1e 100644 --- a/tests/XrdSsiTests/CMakeLists.txt +++ b/tests/XrdSsiTests/CMakeLists.txt @@ -8,7 +8,7 @@ add_executable( target_link_libraries( xrdshmap - ${ZLIB_LIBRARIES} + ZLIB::ZLIB XrdSsiShMap ) #------------------------------------------------------------------------------- diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 6258c0229ca..c40c234e990 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -12,7 +12,7 @@ target_link_libraries( XrdClTestsHelper ${CMAKE_THREAD_LIBS_INIT} ${CPPUNIT_LIBRARIES} - ${ZLIB_LIBRARIES} + ZLIB::ZLIB XrdCl XrdUtils) From aebca47d845c04c6245d9a12bfdc9adc700e67ad Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 23 Feb 2023 10:40:17 +0100 Subject: [PATCH 231/773] [CMake] Use CMake imported target for OpenSSL --- src/XrdCl/CMakeLists.txt | 4 ++-- src/XrdCrypto.cmake | 5 ++--- src/XrdHttp.cmake | 13 ++++--------- src/XrdMacaroons.cmake | 4 ++-- src/XrdSec.cmake | 2 +- src/XrdSecgsi.cmake | 4 ++-- src/XrdUtils.cmake | 2 +- tests/XrdClTests/tls/CMakeLists.txt | 2 +- 8 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index 41c59af762f..2179019af8c 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -113,10 +113,10 @@ target_link_libraries( XrdUtils uuid::uuid ZLIB::ZLIB + OpenSSL::SSL ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBS} - ${CMAKE_DL_LIBS} - ${OPENSSL_LIBRARIES}) + ${CMAKE_DL_LIBS}) set_target_properties( XrdCl diff --git a/src/XrdCrypto.cmake b/src/XrdCrypto.cmake index 4aaba96a82b..e820e433e4f 100644 --- a/src/XrdCrypto.cmake +++ b/src/XrdCrypto.cmake @@ -66,7 +66,7 @@ add_library( target_link_libraries( XrdCryptoLite XrdUtils - ${OPENSSL_CRYPTO_LIBRARY} ) + OpenSSL::Crypto ) set_target_properties( XrdCryptoLite @@ -79,7 +79,6 @@ set_target_properties( #------------------------------------------------------------------------------- # The XrdCryptossl module #------------------------------------------------------------------------------- -include_directories( ${OPENSSL_INCLUDE_DIR} ) set( XrdCryptosslSources XrdCrypto/XrdCryptosslAux.cc XrdCrypto/XrdCryptosslAux.hh @@ -103,7 +102,7 @@ target_link_libraries( XrdCrypto XrdUtils ${CMAKE_THREAD_LIBS_INIT} - ${OPENSSL_LIBRARIES} ) + OpenSSL::SSL ) set_target_properties( ${LIB_XRD_CRYPTOSSL} diff --git a/src/XrdHttp.cmake b/src/XrdHttp.cmake index a5d604ae107..ec3d934717a 100644 --- a/src/XrdHttp.cmake +++ b/src/XrdHttp.cmake @@ -16,7 +16,6 @@ if( BUILD_HTTP ) #----------------------------------------------------------------------------- # The XrdHttp library #----------------------------------------------------------------------------- - include_directories( ${OPENSSL_INCLUDE_DIR} ) set( XrdHttpSources XrdHttp/XrdHttpProtocol.cc XrdHttp/XrdHttpProtocol.hh @@ -47,8 +46,8 @@ if( BUILD_HTTP ) XrdCrypto ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} - ${OPENSSL_LIBRARIES} - ${OPENSSL_CRYPTO_LIBRARY} ) + OpenSSL::SSL + OpenSSL::Crypto ) target_link_libraries( ${MOD_XRD_HTTP} @@ -59,16 +58,12 @@ if( BUILD_HTTP ) ${LIB_XRD_HTTP_UTILS} PROPERTIES VERSION ${XRD_HTTP_UTILS_VERSION} - SOVERSION ${XRD_HTTP_UTILS_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_HTTP_UTILS_SOVERSION}) set_target_properties( ${MOD_XRD_HTTP} PROPERTIES - INTERFACE_LINK_LIBRARIES "" - SUFFIX ".so" - LINK_INTERFACE_LIBRARIES "" ) + SUFFIX ".so") #----------------------------------------------------------------------------- # Install diff --git a/src/XrdMacaroons.cmake b/src/XrdMacaroons.cmake index 148ba914481..837bfe22e16 100644 --- a/src/XrdMacaroons.cmake +++ b/src/XrdMacaroons.cmake @@ -10,7 +10,7 @@ set( LIB_XRD_MACAROONS XrdMacaroons-${PLUGIN_VERSION} ) #------------------------------------------------------------------------------- if( BUILD_MACAROONS ) - include_directories(${MACAROONS_INCLUDES} ${JSON_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR}) + include_directories(${MACAROONS_INCLUDES} ${JSON_INCLUDE_DIRS}) add_library( ${LIB_XRD_MACAROONS} @@ -29,7 +29,7 @@ if( BUILD_MACAROONS ) ${MACAROONS_LIB} ${JSON_LIBRARIES} ${XROOTD_HTTP_LIB} - ${OPENSSL_CRYPTO_LIBRARY}) + OpenSSL::Crypto) if( MacOSX ) SET( MACAROONS_LINK_FLAGS "-Wl") diff --git a/src/XrdSec.cmake b/src/XrdSec.cmake index e17e923a989..0b902f9401d 100644 --- a/src/XrdSec.cmake +++ b/src/XrdSec.cmake @@ -60,7 +60,7 @@ target_link_libraries( ${LIB_XRD_SEC_PROT} XrdUtils ${CMAKE_THREAD_LIBS_INIT} - ${OPENSSL_CRYPTO_LIBRARY} ) + OpenSSL::Crypto ) set_target_properties( ${LIB_XRD_SEC_PROT} diff --git a/src/XrdSecgsi.cmake b/src/XrdSecgsi.cmake index 1b01ebc9a04..a6127918d9e 100644 --- a/src/XrdSecgsi.cmake +++ b/src/XrdSecgsi.cmake @@ -82,7 +82,7 @@ target_link_libraries( xrdgsiproxy XrdCrypto XrdUtils - ${OPENSSL_CRYPTO_LIBRARY} ) + OpenSSL::Crypto ) #------------------------------------------------------------------------------- # xrdgsitest @@ -95,7 +95,7 @@ target_link_libraries( xrdgsitest XrdCrypto XrdUtils - ${OPENSSL_CRYPTO_LIBRARY} ) + OpenSSL::Crypto ) endif() #------------------------------------------------------------------------------- diff --git a/src/XrdUtils.cmake b/src/XrdUtils.cmake index 2a482ab0375..05b4146d92b 100644 --- a/src/XrdUtils.cmake +++ b/src/XrdUtils.cmake @@ -291,9 +291,9 @@ add_library( target_link_libraries( XrdUtils + OpenSSL::SSL ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} - ${OPENSSL_LIBRARIES} ${SOCKET_LIBRARY} ${SENDFILE_LIBRARY} ${EXTRA_LIBS} ) diff --git a/tests/XrdClTests/tls/CMakeLists.txt b/tests/XrdClTests/tls/CMakeLists.txt index e7431a73bf6..7ab152723c0 100644 --- a/tests/XrdClTests/tls/CMakeLists.txt +++ b/tests/XrdClTests/tls/CMakeLists.txt @@ -27,7 +27,7 @@ add_executable( target_link_libraries( xrdsrv-tls XrdUtils - ${OPENSSL_LIBRARIES} + OpenSSL::SSL ${CMAKE_THREAD_LIBS_INIT} ) endif() From 18183b49a199b68d51d07f0e22c291a788b63298 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 23 Feb 2023 11:31:47 +0100 Subject: [PATCH 232/773] [CI] Simplify CMake command for macOS build in GitLab CI --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b4aee001614..b362361d61e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -294,7 +294,7 @@ build:macosx: - 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.2t/include/ -D OPENSSL_SSL_LIBRARY=/usr/local/Cellar/openssl/1.0.2t/lib/libssl.dylib -D OPENSSL_CRYPTO_LIBRARY=/usr/local/Cellar/openssl/1.0.2t/lib/libcrypto.1.0.0.dylib -DCMAKE_INSTALL_PREFIX=../tarball/xrootd .. + - cmake -DCMAKE_PREFIX_PATH='/usr/local/opt/zlib;/usr/local/opt/openssl' -DCMAKE_INSTALL_PREFIX=../tarball/xrootd .. - cd src/XrdCl/ - make -j4 - make install From 732c874abca65b0112f011243e0617127d430480 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 13 Mar 2023 15:43:55 +0100 Subject: [PATCH 233/773] [CI] Install GTest on builds with testing enabled --- .github/workflows/build.yml | 22 +++++++++++++++++----- .gitlab-ci.yml | 10 +++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8288fd22a74..971b5d5b494 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,6 +40,7 @@ jobs: fuse-devel \ gcc-c++ \ git \ + gtest-devel \ json-c-devel \ krb5-devel \ libmacaroons-devel \ @@ -131,6 +132,7 @@ jobs: fuse-devel \ gcc-c++ \ git \ + gtest-devel \ json-c-devel \ krb5-devel \ libmacaroons-devel \ @@ -219,6 +221,7 @@ jobs: fuse3-dev \ g++ \ git \ + gtest-dev \ json-c-dev \ krb5-dev \ libxml2-dev \ @@ -310,7 +313,8 @@ jobs: python3-devel \ python3-setuptools \ git \ - cppunit-devel + cppunit-devel \ + gtest-devel yum clean all # Need to use v1 of action as image Git is too old @@ -384,7 +388,8 @@ jobs: python3-devel \ python3-setuptools \ git \ - cppunit-devel + cppunit-devel \ + gtest-devel yum clean all python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel @@ -454,7 +459,8 @@ jobs: python2-setuptools \ python2-devel \ git \ - cppunit-devel + cppunit-devel \ + gtest-devel yum clean all # Need to use v1 of action as image Git is too old @@ -512,6 +518,8 @@ jobs: cmake \ uuid-dev \ dpkg-dev \ + libcppunit-dev \ + libgtest-dev \ libssl-dev \ libx11-dev \ python3 \ @@ -567,8 +575,10 @@ jobs: run: | brew install \ cmake \ + cppunit \ make \ gcc \ + googletest \ zlib \ krb5 \ ossp-uuid \ @@ -846,7 +856,8 @@ jobs: python3-setuptools \ git \ tree \ - cppunit-devel + cppunit-devel \ + gtest-devel yum clean all python3 -m pip --no-cache-dir install wheel @@ -902,7 +913,8 @@ jobs: python3-setuptools \ git \ tree \ - cppunit-devel + cppunit-devel \ + gtest-devel yum clean all python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b362361d61e..25244c47127 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -64,7 +64,7 @@ build:cs9: script: - dnf install -y epel-release - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git - - dnf install -y cppunit-devel + - 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 @@ -325,7 +325,7 @@ release:cs8-x86_64: - 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 + - dnf install -y cppunit-devel gtest-devel - dnf -y update libarchive - mkdir cs-8-x86_64 - ./gen-tarball.sh $CI_COMMIT_TAG @@ -357,7 +357,7 @@ release:rocky8-x86_64: - 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 + - dnf install -y cppunit-devel gtest-devel - dnf -y update libarchive - mkdir rocky-8-x86_64 - ./gen-tarball.sh $CI_COMMIT_TAG @@ -475,7 +475,7 @@ weekly:cs8: - 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 + - 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} @@ -508,7 +508,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 python-srpm-macros centos-release-scl + - yum install --nogpg -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 From 5aa5b7d2bdd57586e1e034ed0cc830e439d8608f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 13 Mar 2023 15:50:01 +0100 Subject: [PATCH 234/773] [RPM] Add GTest as build dependency when tests are enabled --- packaging/rhel/xrootd.spec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index d27134ea039..7843654d51c 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -119,6 +119,7 @@ BuildRequires: selinux-policy-devel %if %{?_with_tests:1}%{!?_with_tests:0} BuildRequires: cppunit-devel +BuildRequires: gtest-devel %endif %if %{?_with_ceph:1}%{!?_with_ceph:0} From 40b4e63e0fafdb52ef384261d28d8cd4b94d8efb Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 13 Mar 2023 15:52:16 +0100 Subject: [PATCH 235/773] [CMake] Require GTest to be found to enable building tests --- cmake/XRootDFindLibs.cmake | 4 +++- cmake/XRootDSummary.cmake | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 6ee71c5712d..7e98653b97f 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -73,10 +73,12 @@ endif() if( ENABLE_TESTS ) if( FORCE_ENABLED ) find_package( CppUnit REQUIRED ) + find_package( GTest REQUIRED ) else() find_package( CppUnit ) + find_package( GTest ) endif() - if( CPPUNIT_FOUND ) + if( CPPUNIT_FOUND AND GTEST_FOUND ) set( BUILD_TESTS TRUE ) else() set( BUILD_TESTS FALSE ) diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index 94ba80fac4d..a64fcac3e98 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -7,7 +7,7 @@ component_status( FUSE BUILD_FUSE FUSE_FOUND ) component_status( KRB5 BUILD_KRB5 KERBEROS5_FOUND ) component_status( XRDCL ENABLE_XRDCL TRUE_VAR ) component_status( XRDCLHTTP ENABLE_XRDCLHTTP DAVIX_FOUND ) -component_status( TESTS BUILD_TESTS CPPUNIT_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 ) From 672ec5f690a65aa546b1dc2bcbda5104f6d764d2 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Mar 2023 15:07:03 +0100 Subject: [PATCH 236/773] [Tests] Convert tests of XrdCl::URL from CppUnit to Google Test --- tests/CMakeLists.txt | 2 + tests/XrdCl/CMakeLists.txt | 18 ++++ tests/XrdCl/XrdClURL.cc | 131 ++++++++++++++++++++++ tests/XrdClTests/UtilsTest.cc | 197 ---------------------------------- 4 files changed, 151 insertions(+), 197 deletions(-) create mode 100644 tests/XrdCl/CMakeLists.txt create mode 100644 tests/XrdCl/XrdClURL.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b1c0171075c..4a9b7923676 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,5 @@ +include(GoogleTest) +add_subdirectory( XrdCl ) add_subdirectory( common ) add_subdirectory( XrdClTests ) diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt new file mode 100644 index 00000000000..a114c4cb620 --- /dev/null +++ b/tests/XrdCl/CMakeLists.txt @@ -0,0 +1,18 @@ + +add_executable(xrdcl-unit-tests + XrdClURL.cc +) + +target_link_libraries(xrdcl-unit-tests + XrdCl + XrdXml + XrdUtils + GTest::GTest + GTest::Main +) + +target_include_directories(xrdcl-unit-tests + PRIVATE ${CMAKE_SOURCE_DIR}/src +) + +gtest_discover_tests(xrdcl-unit-tests TEST_PREFIX XrdCl::) diff --git a/tests/XrdCl/XrdClURL.cc b/tests/XrdCl/XrdClURL.cc new file mode 100644 index 00000000000..24e7fdc2aad --- /dev/null +++ b/tests/XrdCl/XrdClURL.cc @@ -0,0 +1,131 @@ +#undef NDEBUG + +#include +#include + +#include +#include + +using namespace testing; + +// Test that XrdCl::URL conforms to RFC1738 (https://www.rfc-editor.org/rfc/rfc1738) + +class URLTest : public ::testing::Test {}; + +TEST(URLTest, LocalURLs) +{ + char url[PATH_MAX]; + for (const auto& protocol : { "", "file://" }) { + for (const auto& path : { "/dev", "/dev/", "/dev/null" }) { + for (const auto& params : { "", "?param=value", "?param1=value1¶m2=value2" }) { + snprintf(url, sizeof(url), "%s%s%s", protocol, path, params); + + XrdCl::URL local_url(url); + + EXPECT_TRUE(local_url.IsValid()) << "URL " << url << " is invalid" << std::endl; + + EXPECT_EQ(local_url.GetProtocol(), "file"); + + EXPECT_EQ(local_url.GetHostId(), "localhost"); + EXPECT_EQ(local_url.GetHostName(), "localhost"); + + const char *resolved_path = realpath(path, nullptr); + snprintf(url, sizeof(url), "%s%s", resolved_path, params); + + EXPECT_STREQ(local_url.GetPath().c_str(), resolved_path); + EXPECT_STREQ(local_url.GetPathWithParams().c_str(), url); + EXPECT_STREQ(local_url.GetParamsAsString().c_str(), params); + + free((void*)resolved_path); + } + } + } +} + +TEST(URLTest, RemoteURLs) +{ + char url[PATH_MAX]; + char path_params[PATH_MAX]; + 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" }) { + for (const char *password : { "", "abc ABC 123", "symbols \\~`!#$%^&*()_-+={[}]|:;'\"<,>.?" }) { + for (const char *host : { "localhost", "[::1]", "127.0.0.1", "eospilot.cern.ch" }) { + for (const char *port : { "", "-1", "1094", "9999", "65535" }) { + for (const char *path : { "", "/", "/data", "/data/", "/data/file.dat", "/data//file" }) { + for (const char *params : { "", "?param=value", "?param1=value1¶m2=value2" }) { + snprintf(url, sizeof(url), "%s%s%s%s%s%s%s%s%s%s%s%s", + protocol, *protocol ? "://" : "", + // TODO: allow empty user and/or password in the login part + user, *user && *password ? ":" : "", *user ? password : "", *user ? "@" : "", + // TODO: accept URLs with empty path and non-empty parameters + host, *port ? ":" : "", port, *params && !*path ? "/" : "", path, params); + snprintf(path_params, sizeof(path_params), "%s%s", *path == '/' ? path+1 : path, params); + + XrdCl::URL remote_url(url); + + EXPECT_TRUE(remote_url.IsValid()) << "URL " << url << " is invalid" << std::endl; + EXPECT_EQ(remote_url.GetPort(), *port ? atoi(port) : default_port); + EXPECT_STREQ(remote_url.GetProtocol().c_str(), *protocol ? protocol : "root"); + EXPECT_STREQ(remote_url.GetPassword().c_str(), *user && *password ? password : ""); + EXPECT_STREQ(remote_url.GetHostName().c_str(), host); + EXPECT_STREQ(remote_url.GetPath().c_str(), *path == '/' ? path+1 : path); + EXPECT_STREQ(remote_url.GetParamsAsString().c_str(), params); + EXPECT_STREQ(remote_url.GetPathWithParams().c_str(), path_params); + } + } + } + } + } + } + } + + XrdCl::URL complex_url( + /* protocol, login, host, and port */ + "root://xrootd:fxG}+u;B@lxfsra02a08.cern.ch:9999/" + /* path */ + "/eos/dev/SMWZd3pdExample_NTUP_SMWZ.526666._000073.root.1?" + /* parameters */ + "&cap.sym=sfdDqALWo3W3tWUJ2O5XwQ5GG8U=" + "&cap.msg=eGj/mh+9TrecFBAZBNr/nLau4p0kjlEOjc1JC+9DVjL1Tq+g" + "eBCIz/kKs261mnL4dJeUu6r25acCn4vhyp8UKyL1cVmmnyBnjqe6tz28q" + "FO2#0fQHrHf6Z9N0MNhw1fplYjpGeNwFH2jQSfSo24zSZKGa/PKClGYnX" + "&mgm.loginid=766877e6-9874-11e1-a77f-003048cf8cd8" + "&mgm.replicaindex=0" + "&mgm.replicahead=1" + ); + + EXPECT_TRUE(complex_url.IsValid()); + EXPECT_EQ(complex_url.GetPort(), 9999); + EXPECT_STREQ(complex_url.GetProtocol().c_str(), "root"); + EXPECT_STREQ(complex_url.GetUserName().c_str(), "xrootd"); + EXPECT_STREQ(complex_url.GetPassword().c_str(), "fxG}+u;B"); + EXPECT_STREQ(complex_url.GetHostName().c_str(), "lxfsra02a08.cern.ch"); + EXPECT_STREQ(complex_url.GetPath().c_str(), "/eos/dev/SMWZd3pdExample_NTUP_SMWZ.526666._000073.root.1"); + EXPECT_EQ(complex_url.GetParams().size(), 5); + + auto params = complex_url.GetParams(); + + EXPECT_EQ(params["cap.sym"], "sfdDqALWo3W3tWUJ2O5XwQ5GG8U="); + EXPECT_EQ(params["mgm.loginid"], "766877e6-9874-11e1-a77f-003048cf8cd8"); + EXPECT_EQ(params["mgm.replicaindex"], "0"); + EXPECT_EQ(params["mgm.replicahead"], "1"); +} + +TEST(URLTest, InvalidURLs) +{ + const char *invalid_urls[] = { + "root://", + "://asds", + "root:////path?param1=val1¶m2=val2", + "root://@//path?param1=val1¶m2=val2", + "root://:@//path?param1=val1¶m2=val2", + "root://asd@://path?param1=val1¶m2=val2" + "root://user1:passwd1host1:123//path?param1=val1¶m2=val2", + "root://user1:passwd1@host1:asd//path?param1=val1¶m2=val2", + }; + + for (const auto& url : invalid_urls) + EXPECT_FALSE(XrdCl::URL(url).IsValid()) << "URL " << url << " is not invalid" << std::endl; +} + diff --git a/tests/XrdClTests/UtilsTest.cc b/tests/XrdClTests/UtilsTest.cc index 368ca63ddd9..86104bc57f7 100644 --- a/tests/XrdClTests/UtilsTest.cc +++ b/tests/XrdClTests/UtilsTest.cc @@ -24,7 +24,6 @@ #include #include "CppUnitXrdHelpers.hh" -#include "XrdCl/XrdClURL.hh" #include "XrdCl/XrdClAnyObject.hh" #include "XrdCl/XrdClTaskManager.hh" #include "XrdCl/XrdClSIDManager.hh" @@ -37,13 +36,11 @@ 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(); @@ -52,200 +49,6 @@ class UtilsTest: public CppUnit::TestCase 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: From 9495def5e1a6472246ba014d52bdd69afe6194ee Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Mar 2023 15:18:16 +0100 Subject: [PATCH 237/773] [CMake] Add XrdCl tests to CMake test target --- tests/XrdClTests/CMakeLists.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/XrdClTests/CMakeLists.txt b/tests/XrdClTests/CMakeLists.txt index 3206a39d0ce..b1603333c5e 100644 --- a/tests/XrdClTests/CMakeLists.txt +++ b/tests/XrdClTests/CMakeLists.txt @@ -45,6 +45,22 @@ target_link_libraries( XrdClTestsHelper XrdCl ) +foreach(TEST_SUITE + # File + # FileCopy + # FileSystem + # LocalFileHandler + Poller + # PostMaster + Socket + # Threading + Utils + # Workflow +) + add_test(NAME XrdCl::${TEST_SUITE} + COMMAND $ $ "All Tests/${TEST_SUITE}Test") +endforeach() + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- From bf269577202a691e2d52e77b6f310070d86f6009 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Mar 2023 15:29:57 +0100 Subject: [PATCH 238/773] [CI] Run unit tests using CTest --- .github/workflows/build.yml | 42 ++++++++++++------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 971b5d5b494..2f55446378b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -101,15 +101,9 @@ jobs: python3 -c 'import pyxrootd; print(pyxrootd)' python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - name: Run libXrdCl tests + - name: Run tests with CTest run: | - cd ../build/tests - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/UtilsTest/' - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/SocketTest/' - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/PollerTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/PostMasterTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/FileSystemTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/LocalFileHandlerTest/' + ctest --output-on-failure --test-dir ../build cmake-almalinux9: @@ -193,15 +187,9 @@ jobs: python3 -c 'import pyxrootd; print(pyxrootd)' python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - name: Run libXrdCl tests + - name: Run tests with CTest run: | - cd ../build/tests - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/UtilsTest/' - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/SocketTest/' - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/PollerTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/PostMasterTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/FileSystemTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/LocalFileHandlerTest/' + ctest --output-on-failure --test-dir ../build cmake-alpine-musl: @@ -282,12 +270,9 @@ jobs: python3 -c 'import pyxrootd; print(pyxrootd)' python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - name: Run libXrdCl tests + - name: Run tests with CTest run: | - cd ../build/tests - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/UtilsTest/' - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/SocketTest/' - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/PollerTest/' + ctest --output-on-failure --test-dir ../build cmake-centos7: @@ -354,15 +339,10 @@ jobs: python3 -c 'import pyxrootd; print(pyxrootd)' python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - name: Run libXrdCl tests + - name: Run tests with CTest run: | - cd ../build/tests - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/UtilsTest/' - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/SocketTest/' - ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/PollerTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/PostMasterTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/FileSystemTest/' - # ./common/test-runner ./XrdClTests/libXrdClTests.so 'All Tests/LocalFileHandlerTest/' + cd ../build + ctest3 --output-on-failure cmake-centos7-updated-python: @@ -619,6 +599,10 @@ jobs: python3 -m pip install --user build/bindings/python python3 -m pip list + - name: Run tests with CTest + run: | + ctest --output-on-failure --test-dir ../build + - name: Verify install run: | command -v xrootd From 2a5147074e02783f665e23f7b969051f879a54df Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 14 Mar 2023 15:35:10 +0100 Subject: [PATCH 239/773] [RPM] Add %check stage to run unit tests during build --- packaging/rhel/xrootd.spec.in | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index 7843654d51c..3ddc5b01fe4 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -582,6 +582,14 @@ pushd build/bindings/python %endif popd +%check +cd xrootd/build +%if %{use_cmake3} +ctest3 --output-on-failure +%else +ctest --output-on-failure +%endif + #------------------------------------------------------------------------------- # Installation #------------------------------------------------------------------------------- From 89d65556c6d86777779a84e698222b1d7c515e39 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 20 Mar 2023 15:17:45 +0100 Subject: [PATCH 240/773] [XrdHttp] Proper handling of SSL_Shutdown() return code --- src/XrdHttp/XrdHttpProtocol.cc | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 40c455656e5..15fc09b10a2 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1758,11 +1758,26 @@ void XrdHttpProtocol::Cleanup() { } if (ssl) { - - - if (SSL_shutdown(ssl) != 1) { - TRACE(ALL, " SSL_shutdown failed"); - ERR_print_errors(sslbio_err); + // Shutdown the SSL/TLS connection + // https://www.openssl.org/docs/man1.0.2/man3/SSL_shutdown.html + // We don't need a bidirectional shutdown as + // when we are here, the connection will not be re-used. + // In the case SSL_shutdown returns 0, + // "the output of SSL_get_error(3) may be misleading, as an erroneous SSL_ERROR_SYSCALL may be flagged even though no error occurred." + // we will then just flush the thread's queue. + // In the case an error really happened, we print the error that happened + int ret = SSL_shutdown(ssl); + if (ret != 1) { + if(ret == 0) { + // Clean this thread's error queue for the old openssl versions + #if OPENSSL_VERSION_NUMBER < 0x10100000L + ERR_remove_thread_state(nullptr); + #endif + } else { + //ret < 0, an error really happened. + TRACE(ALL, " SSL_shutdown failed"); + ERR_print_errors(sslbio_err); + } } if (secxtractor) From a05db49158e8b29d906928035ee8c1ef5d14c30b Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 21 Mar 2023 14:59:46 +0100 Subject: [PATCH 241/773] [XrdHttp] XrdHttpProtocol - Calls to ERR_print_errors() is done only when an error occurred --- src/XrdHttp/XrdHttpProtocol.cc | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 15fc09b10a2..109dd9fc228 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -526,22 +526,23 @@ int XrdHttpProtocol::Process(XrdLink *lp) // We ignore the argument here TRACEI(DEBUG, " Entering SSL_accept..."); int res = SSL_accept(ssl); TRACEI(DEBUG, " SSL_accept returned :" << res); - ERR_print_errors(sslbio_err); - if ((res == -1) && (SSL_get_error(ssl, res) == SSL_ERROR_WANT_READ)) { TRACEI(DEBUG, " SSL_accept wants to read more bytes... err:" << SSL_get_error(ssl, res)); return 1; } - if (res < 0) { + if(res <= 0) { ERR_print_errors(sslbio_err); - SSL_free(ssl); - ssl = 0; - return -1; - } + if (res < 0) { + + SSL_free(ssl); + ssl = 0; + return -1; + } + } BIO_set_nbio(sbio, 0); - ERR_print_errors(sslbio_err); + strcpy(SecEntity.prot, "https"); // Get the voms string and auth information From 54cecf73051931d60e8dd1f6a6cb4d092ce2daf9 Mon Sep 17 00:00:00 2001 From: Elvin Sindrilaru Date: Tue, 21 Mar 2023 13:46:26 +0100 Subject: [PATCH 242/773] [XrdHttp] Avoid sending empty header in the response as this conflicts with the reported content-length --- src/XrdHttp/XrdHttpProtocol.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 109dd9fc228..4a8ac2092b5 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1529,7 +1529,7 @@ int XrdHttpProtocol::StartSimpleResp(int code, const char *desc, const char *hea if ((bodylen >= 0) && (code != 100)) ss << "Content-Length: " << bodylen << crlf; - if (header_to_add) + if (header_to_add && (header_to_add[0] != '\0')) ss << header_to_add << crlf; ss << crlf; @@ -1548,13 +1548,13 @@ int XrdHttpProtocol::StartSimpleResp(int code, const char *desc, const char *hea int XrdHttpProtocol::StartChunkedResp(int code, const char *desc, const char *header_to_add, long long bodylen, bool keepalive) { const std::string crlf = "\r\n"; - std::stringstream ss; - if (header_to_add) { + + if (header_to_add && (header_to_add[0] != '\0')) { ss << header_to_add << crlf; } - ss << "Transfer-Encoding: chunked"; + ss << "Transfer-Encoding: chunked"; TRACEI(RSP, "Starting chunked response"); return StartSimpleResp(code, desc, ss.str().c_str(), bodylen, keepalive); } From 6e703cb3c807bcd32a73564b07db02c915c58e40 Mon Sep 17 00:00:00 2001 From: Gerardo GANIS Date: Wed, 22 Mar 2023 08:36:45 +0100 Subject: [PATCH 243/773] Fix for the time parsing related issue (#1969) Allow for GeneralizedTime (> year 2049) in certificates. Fix also a bug in renormalizing the Year from UTCTime parsing. Fixes #1969. --- src/XrdCrypto/XrdCryptosslAux.cc | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/XrdCrypto/XrdCryptosslAux.cc b/src/XrdCrypto/XrdCryptosslAux.cc index 161dc2399bd..942d86b862d 100644 --- a/src/XrdCrypto/XrdCryptosslAux.cc +++ b/src/XrdCrypto/XrdCryptosslAux.cc @@ -730,16 +730,27 @@ time_t XrdCryptosslASN1toUTC(const ASN1_TIME *tsn1) &(ltm.tm_year), &(ltm.tm_mon), &(ltm.tm_mday), &(ltm.tm_hour), &(ltm.tm_min), &(ltm.tm_sec), &zz) != 7) || (zz != 'Z')) { - return -1; + // Try GeneralizedTime + if ((sscanf((const char *)(tsn1->data), + "%04d%02d%02d%02d%02d%02d%c", + &(ltm.tm_year), &(ltm.tm_mon), &(ltm.tm_mday), + &(ltm.tm_hour), &(ltm.tm_min), &(ltm.tm_sec), + &zz) != 7) || (zz != 'Z')) { + return -1; + } } // Init also the ones not used by mktime ltm.tm_wday = 0; // day of the week ltm.tm_yday = 0; // day in the year ltm.tm_isdst = 0; // we will correct with an offset without dst // - // Renormalize some values: year should be modulo 1900 - if (ltm.tm_year < 90) - ltm.tm_year += 100; + // Renormalize some values (year should be modulo 1900), honouring all cases + if (ltm.tm_year < 50) { + ltm.tm_year += 2000; + } else if (ltm.tm_year < 100) { + ltm.tm_year += 1900; + } + ltm.tm_year -= 1900; // // month should in [0, 11] (ltm.tm_mon)--; From d1461f8ab41a70e7ae58d632171fa6d30bf3d00a Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 22 Mar 2023 10:06:49 +0100 Subject: [PATCH 244/773] [XrdHttp] Clear digest header between requests --- src/XrdHttp/XrdHttpReq.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 6732578cdfb..99e7dba61d5 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -2977,6 +2977,7 @@ void XrdHttpReq::reset() { // Reset the state of the request's digest request. m_req_digest.clear(); + m_digest_header.clear(); m_resource_with_digest = ""; headerok = false; From 39d6f8f2e3e5c5809688d5d1c3a20101c3da833a Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 22 Mar 2023 13:43:22 +0100 Subject: [PATCH 245/773] [CI] Add workaround to let server run on macOS without proper hostname --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f55446378b..abd2a4ba256 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -579,6 +579,8 @@ jobs: # be given explicitly. - name: Build with cmake 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 \ -DCMAKE_C_COMPILER=clang \ From 4de1d801a8bc86bf182ac4760c71ebd458b15042 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Fri, 10 Mar 2023 09:23:10 +0100 Subject: [PATCH 246/773] [XrdHttp] Q-values presence is considered BUT discarded when pattern matching the digest algorithm name to run --- src/XrdHttp.cmake | 4 +- src/XrdHttp/XrdHttpChecksum.cc | 47 +++++++ src/XrdHttp/XrdHttpChecksum.hh | 54 ++++++++ src/XrdHttp/XrdHttpChecksumHandler.cc | 116 ++++++++++++++++ src/XrdHttp/XrdHttpChecksumHandler.hh | 116 ++++++++++++++++ src/XrdHttp/XrdHttpProtocol.cc | 14 ++ src/XrdHttp/XrdHttpProtocol.hh | 4 + src/XrdHttp/XrdHttpReq.cc | 183 ++++---------------------- src/XrdHttp/XrdHttpReq.hh | 28 ++-- src/XrdOuc/XrdOucUtils.cc | 9 ++ src/XrdOuc/XrdOucUtils.hh | 2 + 11 files changed, 406 insertions(+), 171 deletions(-) create mode 100644 src/XrdHttp/XrdHttpChecksum.cc create mode 100644 src/XrdHttp/XrdHttpChecksum.hh create mode 100644 src/XrdHttp/XrdHttpChecksumHandler.cc create mode 100644 src/XrdHttp/XrdHttpChecksumHandler.hh diff --git a/src/XrdHttp.cmake b/src/XrdHttp.cmake index ec3d934717a..9a0fcc60ba7 100644 --- a/src/XrdHttp.cmake +++ b/src/XrdHttp.cmake @@ -25,7 +25,9 @@ if( BUILD_HTTP ) XrdHttp/XrdHttpExtHandler.cc XrdHttp/XrdHttpExtHandler.hh XrdHttp/XrdHttpStatic.hh XrdHttp/XrdHttpTrace.hh - XrdHttp/XrdHttpUtils.cc XrdHttp/XrdHttpUtils.hh ) + XrdHttp/XrdHttpUtils.cc XrdHttp/XrdHttpUtils.hh + XrdHttp/XrdHttpChecksumHandler.cc XrdHttp/XrdHttpChecksumHandler.hh + XrdHttp/XrdHttpChecksum.cc XrdHttp/XrdHttpChecksum.hh) # Note this is marked as a shared library as XrdHttp plugins are expected to # link against this for the XrdHttpExt class implementations. diff --git a/src/XrdHttp/XrdHttpChecksum.cc b/src/XrdHttp/XrdHttpChecksum.cc new file mode 100644 index 00000000000..d4c6f21a51f --- /dev/null +++ b/src/XrdHttp/XrdHttpChecksum.cc @@ -0,0 +1,47 @@ +//------------------------------------------------------------------------------ +// 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) +// Author: Cedric Caffy +// File Date: Mar 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 "XrdHttpChecksum.hh" +#include + +XrdHttpChecksum::XrdHttpChecksum(const std::string & xrootConfigDigestName, const std::string & httpName, bool needsBase64Padding): + mXRootDConfigDigestName(xrootConfigDigestName), + mHTTPName(httpName), + mNeedsBase64Padding(needsBase64Padding){} + +std::string XrdHttpChecksum::getHttpName() const { + return mHTTPName; +} + +std::string XrdHttpChecksum::getHttpNameLowerCase() const { + std::string ret = getHttpName(); + std::transform(ret.begin(),ret.end(),ret.begin(),::tolower); + return ret; +} + +std::string XrdHttpChecksum::getXRootDConfigDigestName() const { + return mXRootDConfigDigestName; +} + +bool XrdHttpChecksum::needsBase64Padding() const { + return mNeedsBase64Padding; +} \ No newline at end of file diff --git a/src/XrdHttp/XrdHttpChecksum.hh b/src/XrdHttp/XrdHttpChecksum.hh new file mode 100644 index 00000000000..b997dbc97b5 --- /dev/null +++ b/src/XrdHttp/XrdHttpChecksum.hh @@ -0,0 +1,54 @@ +//------------------------------------------------------------------------------ +// 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) +// Author: Cedric Caffy +// File Date: Mar 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_XRDHTTPCHECKSUM_HH +#define XROOTD_XRDHTTPCHECKSUM_HH + +#include + +/** + * Simple object containing information about + * a checksum + */ +class XrdHttpChecksum { +public: + /** + * Constructor + * @param xrootConfigDigestName the name that will be used by XRootD server to run the checksum + * @param httpName the HTTP RFC compliant name of the checksum + * @param needsBase64Padding sets to true if the checksum needs to be base64 encoded before being sent, false otherwise + */ + XrdHttpChecksum(const std::string & xrootConfigDigestName, const std::string & httpName, bool needsBase64Padding); + + std::string getXRootDConfigDigestName() const; + std::string getHttpName() const; + std::string getHttpNameLowerCase() const; + bool needsBase64Padding() const; + +private: + std::string mXRootDConfigDigestName; + std::string mHTTPName; + bool mNeedsBase64Padding; +}; + + +#endif //XROOTD_XRDHTTPCHECKSUM_HH diff --git a/src/XrdHttp/XrdHttpChecksumHandler.cc b/src/XrdHttp/XrdHttpChecksumHandler.cc new file mode 100644 index 00000000000..2bd2af9de0a --- /dev/null +++ b/src/XrdHttp/XrdHttpChecksumHandler.cc @@ -0,0 +1,116 @@ +//------------------------------------------------------------------------------ +// 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) +// Author: Cedric Caffy +// File Date: Mar 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 "XrdHttpChecksumHandler.hh" +#include "XrdOuc/XrdOucTUtils.hh" +#include "XrdOuc/XrdOucUtils.hh" +#include +#include + +std::map XrdHttpChecksumHandlerImpl::XROOTD_DIGEST_NAME_TO_CKSUMS; + +void XrdHttpChecksumHandlerImpl::initializeCksumsMaps() { + addChecksumToMaps(std::make_unique("md5","md5",true)); + addChecksumToMaps(std::make_unique("adler32","adler32",false)); + addChecksumToMaps(std::make_unique("sha1","sha",true)); + addChecksumToMaps(std::make_unique("sha256","sha-256",true)); + addChecksumToMaps(std::make_unique("sha512","sha-512",true)); + addChecksumToMaps(std::make_unique("cksum","UNIXcksum",false)); + addChecksumToMaps(std::make_unique("crc32","crc32",false)); + addChecksumToMaps(std::make_unique("crc32c","crc32c",true)); +} + +void XrdHttpChecksumHandlerImpl::addChecksumToMaps(XrdHttpChecksumHandlerImpl::XrdHttpChecksumPtr && checksum) { + // We also map xrootd-configured checksum's HTTP names to the corresponding checksums --> this will allow + // users to configure algorithms like, for example, `sha-512` and be considered as `sha512` algorithm + XROOTD_DIGEST_NAME_TO_CKSUMS[checksum->getHttpNameLowerCase()] = std::make_unique(checksum->getHttpNameLowerCase(), checksum->getHttpName(), checksum->needsBase64Padding()); + XROOTD_DIGEST_NAME_TO_CKSUMS[checksum->getXRootDConfigDigestName()] = std::move(checksum); +} + +XrdHttpChecksumHandlerImpl::XrdHttpChecksumRawPtr XrdHttpChecksumHandlerImpl::getChecksumToRun(const std::string &userDigestIn) const { + if(!mConfiguredChecksums.empty()) { + std::vector userDigests = getUserDigests(userDigestIn); + //Loop over the user digests and find the corresponding checksum + for(auto userDigest: userDigests) { + auto httpCksum = std::find_if(mConfiguredChecksums.begin(), mConfiguredChecksums.end(),[userDigest](const XrdHttpChecksumRawPtr & cksum){ + return userDigest == cksum->getHttpNameLowerCase(); + }); + if(httpCksum != mConfiguredChecksums.end()) { + return *httpCksum; + } + } + return mConfiguredChecksums[0]; + } + //If there are no configured checksums, return nullptr + return nullptr; +} + +const std::vector &XrdHttpChecksumHandlerImpl::getNonIANAConfiguredCksums() const { + return mNonIANAConfiguredChecksums; +} + +const std::vector & XrdHttpChecksumHandlerImpl::getConfiguredChecksums() const { + return mConfiguredChecksums; +} + + +void XrdHttpChecksumHandlerImpl::configure(const char *csList) { + initializeCksumsMaps(); + if(csList != nullptr) { + initializeXRootDConfiguredCksums(csList); + } +} + +void XrdHttpChecksumHandlerImpl::initializeXRootDConfiguredCksums(const char *csList) { + std::vector splittedCslist; + XrdOucTUtils::splitString(splittedCslist,csList,","); + for(auto csElt: splittedCslist) { + auto csName = getElement(csElt,":",1); + auto checksumItor = XROOTD_DIGEST_NAME_TO_CKSUMS.find(csName); + if(checksumItor != XROOTD_DIGEST_NAME_TO_CKSUMS.end()) { + mConfiguredChecksums.push_back(checksumItor->second.get()); + } else { + mNonIANAConfiguredChecksums.push_back(csName); + } + } +} + +std::string XrdHttpChecksumHandlerImpl::getElement(const std::string &input, const std::string & delimiter, + const size_t position) { + std::vector elementsAfterSplit; + XrdOucTUtils::splitString(elementsAfterSplit,input,delimiter); + return elementsAfterSplit[position]; +} + +std::vector XrdHttpChecksumHandlerImpl::getUserDigests(const std::string &userDigests) { + //userDigest is a comma-separated list with q-values + std::vector userDigestsRet; + std::vector userDigestsWithQValues; + XrdOucTUtils::splitString(userDigestsWithQValues,userDigests,","); + for(auto & userDigestWithQValue: userDigestsWithQValues){ + std::transform(userDigestWithQValue.begin(),userDigestWithQValue.end(),userDigestWithQValue.begin(),::tolower); + auto userDigest = getElement(userDigestWithQValue,";",0); + XrdOucUtils::trim(userDigest); + userDigestsRet.push_back(userDigest); + } + return userDigestsRet; +} \ No newline at end of file diff --git a/src/XrdHttp/XrdHttpChecksumHandler.hh b/src/XrdHttp/XrdHttpChecksumHandler.hh new file mode 100644 index 00000000000..b3523259eb6 --- /dev/null +++ b/src/XrdHttp/XrdHttpChecksumHandler.hh @@ -0,0 +1,116 @@ +//------------------------------------------------------------------------------ +// 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) +// Author: Cedric Caffy +// File Date: Mar 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_XRDHTTPCHECKSUMHANDLER_HH +#define XROOTD_XRDHTTPCHECKSUMHANDLER_HH + +#include "XrdHttpChecksum.hh" + +#include +#include +#include +#include + +/** + * Implementation class of the XrdHttpChecksumHandler + * + * Is useful for unit testing + */ +class XrdHttpChecksumHandlerImpl { +public: + using XrdHttpChecksumPtr = std::unique_ptr; + using XrdHttpChecksumRawPtr = XrdHttpChecksum *; + + XrdHttpChecksumHandlerImpl() = default; + + void configure(const char * csList); + XrdHttpChecksumRawPtr getChecksumToRun(const std::string & userDigest) const; + const std::vector & getNonIANAConfiguredCksums() const; + /** + * For testing purposes + */ + const std::vector & getConfiguredChecksums() const; +private: + /** + * Initializes the checksums from the csList parameter passed + * + * The elements of the csList parameter should all be lower-cased + * @param csList the list of the configured checksum under the format 0:adler32,1:sha1,2:sha512 + */ + void initializeXRootDConfiguredCksums(const char *csList); + /** + * Modify this if new checksums have to be supported or + * if some don't require base64 padding anymore + */ + static void initializeCksumsMaps(); + static void addChecksumToMaps(XrdHttpChecksumPtr && checksum); + static std::string getElement(const std::string & input, const std::string & delimiter, const size_t position); + /** + * Returns a vector of user digests (lower-cased) extracted from the userDigests string passed in parameter + * @param userDigests the string containing a quality-valued checksum list e.g: adler32, md5;q=0.4, md5 + * @return the lower-cased user digests vector + */ + static std::vector getUserDigests(const std::string & userDigests); + + //The map that containing all possible IANA-HTTP-compatible xrootd checksums + static std::map XROOTD_DIGEST_NAME_TO_CKSUMS; + // The vector of IANA-HTTP-compatible configured checksum + std::vector mConfiguredChecksums; + // The vector of non-HTTP configured checksum names (for testing purposes) + std::vector mNonIANAConfiguredChecksums; +}; + +/** + * This class allows to handle xrd http checksum algorithm selection + * based on what the user provided as a digest + */ +class XrdHttpChecksumHandler { +public: + using XrdHttpChecksumRawPtr = XrdHttpChecksumHandlerImpl::XrdHttpChecksumRawPtr; + + XrdHttpChecksumHandler() = default; + /** + * Configure this handler. + * @throws runtime_exception if no algorithm in the csList is compatible with HTTP + * @param csList the list coming from the server configuration. Should be under the format 0:adler32,1:sha512 + */ + void configure(const char * csList) { pImpl.configure(csList); } + /** + * Returns the checksum to run from the user "Want-Digest" provided string + * @param userDigest the digest string under the format "sha-512,sha-256;q=0.8,sha;q=0.6,md5;q=0.4,adler32;q=0.2" + * @return the checksum to run depending on the userDigest provided string + * The logic behind it is simple: returns the first userDigest provided that matches the one configured. + * If none is matched, the first algorithm configured on the server side will be returned. + * If no HTTP-IANA compatible checksum algorithm has been configured or NO checksum algorithm have been configured, nullptr will be returned. + */ + XrdHttpChecksumRawPtr getChecksumToRun(const std::string & userDigest) const { return pImpl.getChecksumToRun(userDigest); } + + /** + * Returns the checksums that are incompatible with HTTP --> the ones that + * we do not know whether the result should be base64 encoded or not + */ + const std::vector & getNonIANAConfiguredCksums() const { return pImpl.getNonIANAConfiguredCksums(); } +private: + XrdHttpChecksumHandlerImpl pImpl; +}; + + +#endif //XROOTD_XRDHTTPCHECKSUMHANDLER_HH diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 4a8ac2092b5..feef9d6c447 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; +XrdHttpChecksumHandler XrdHttpProtocol::cksumHandler = XrdHttpChecksumHandler(); XrdSysTrace XrdHttpTrace("http"); @@ -949,6 +950,19 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { char *var; int cfgFD, GoNo, NoGo = 0, ismine; + cksumHandler.configure(xrd_cslist); + auto nonIanaChecksums = cksumHandler.getNonIANAConfiguredCksums(); + if(nonIanaChecksums.size()) { + std::stringstream warningMsgSS; + warningMsgSS << "Config warning: the following checksum algorithms are not IANA compliant: ["; + std::string unknownCksumString; + for(auto unknownCksum: nonIanaChecksums) { + unknownCksumString += unknownCksum + ","; + } + unknownCksumString.erase(unknownCksumString.size() - 1); + warningMsgSS << unknownCksumString << "]" << ". They therefore cannot be queried by a user via HTTP." ; + eDest.Say(warningMsgSS.str().c_str()); + } // Initialize our custom BIO type. if (!m_bio_type) { diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index 7a537c9ab30..0663dfd70c7 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -46,6 +46,7 @@ #include "XrdOuc/XrdOucStream.hh" #include "Xrd/XrdProtocol.hh" #include "XrdOuc/XrdOucHash.hh" +#include "XrdHttpChecksumHandler.hh" #include @@ -125,6 +126,9 @@ public: /// Authentication area XrdSecEntity SecEntity; + // XrdHttp checksum handling class + static XrdHttpChecksumHandler cksumHandler; + /// called via https bool isHTTPS() { return ishttps; } diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 99e7dba61d5..7b7c2b18b4c 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -55,6 +55,7 @@ #include #include #include "XrdOuc/XrdOucTUtils.hh" +#include "XrdOuc/XrdOucUtils.hh" #include "XrdHttpUtils.hh" @@ -71,122 +72,9 @@ namespace const char *TraceID = "Req"; } -static std::string convert_digest_rfc_name(const std::string &rfc_name_multiple) -{ - std::stringstream rfc_name_multiple_ss; - rfc_name_multiple_ss << rfc_name_multiple; - for (std::string rfc_name; std::getline(rfc_name_multiple_ss, rfc_name, ','); ) { - rfc_name.erase(rfc_name.find_last_not_of(" \n\r\t") + 1); - rfc_name.erase(0, rfc_name.find_first_not_of(" \n\r\t")); - rfc_name = rfc_name.substr(0, rfc_name.find(";")); - if (!strcasecmp(rfc_name.c_str(), "md5")) { - return "md5"; - } else if (!strcasecmp(rfc_name.c_str(), "adler32")) { - return "adler32"; - } else if (!strcasecmp(rfc_name.c_str(), "SHA")) { - return "SHA"; - } else if (!strcasecmp(rfc_name.c_str(), "SHA-256")) { - return "SHA-256"; - } else if (!strcasecmp(rfc_name.c_str(), "SHA-512")) { - return "SHA-512"; - } else if (!strcasecmp(rfc_name.c_str(), "UNIXcksum")) { - return "UNIXcksum"; - } else if (!strcasecmp(rfc_name.c_str(), "crc32")) { - return "crc32"; - } else if (!strcasecmp(rfc_name.c_str(), "crc32c")) { - return "crc32c"; - } - } - return "unknown"; -} - - -static XrdOucString convert_digest_name(const std::string &rfc_name_multiple) -{ - std::stringstream rfc_name_multiple_ss; - rfc_name_multiple_ss << rfc_name_multiple; - for (std::string rfc_name; std::getline(rfc_name_multiple_ss, rfc_name, ','); ) { - rfc_name.erase(rfc_name.find_last_not_of(" \n\r\t") + 1); - rfc_name.erase(0, rfc_name.find_first_not_of(" \n\r\t")); - rfc_name = rfc_name.substr(0, rfc_name.find(";")); - if (!strcasecmp(rfc_name.c_str(), "md5")) { - return "md5"; - } else if (!strcasecmp(rfc_name.c_str(), "adler32")) { - return "adler32"; - } else if (!strcasecmp(rfc_name.c_str(), "SHA")) { - return "sha1"; - } else if (!strcasecmp(rfc_name.c_str(), "SHA-256")) { - return "sha256"; - } else if (!strcasecmp(rfc_name.c_str(), "SHA-512")) { - return "sha512"; - } else if (!strcasecmp(rfc_name.c_str(), "UNIXcksum")) { - return "cksum"; - } else if (!strcasecmp(rfc_name.c_str(), "crc32")) { - return "crc32"; - } else if (!strcasecmp(rfc_name.c_str(), "crc32c")) { - return "crc32c"; - } - } - return "unknown"; -} - - -static std::string convert_xrootd_to_rfc_name(const std::string &xrootd_name) -{ - if (!strcasecmp(xrootd_name.c_str(), "md5")) { - return "md5"; - } else if (!strcasecmp(xrootd_name.c_str(), "adler32")) { - return "adler32"; - } else if (!strcasecmp(xrootd_name.c_str(), "sha1")) { - return "SHA"; - } else if (!strcasecmp(xrootd_name.c_str(), "sha256")) { - return "SHA-256"; - } else if (!strcasecmp(xrootd_name.c_str(), "sha512")) { - return "SHA-512"; - } else if (!strcasecmp(xrootd_name.c_str(), "cksum")) { - return "UNIXcksum"; - } else if (!strcasecmp(xrootd_name.c_str(), "crc32")) { - return "crc32"; - } else if (!strcasecmp(xrootd_name.c_str(), "crc32c")) { - return "crc32c"; - } - return "unknown"; -} - - -static bool needs_base64_padding(const std::string &rfc_name) -{ - if (!strcasecmp(rfc_name.c_str(), "md5")) { - return true; - } else if (!strcasecmp(rfc_name.c_str(), "adler32")) { - return false; - } else if (!strcasecmp(rfc_name.c_str(), "SHA")) { - return true; - } else if (!strcasecmp(rfc_name.c_str(), "SHA-256")) { - return true; - } else if (!strcasecmp(rfc_name.c_str(), "SHA-512")) { - return true; - } else if (!strcasecmp(rfc_name.c_str(), "UNIXcksum")) { - return false; - } else if (!strcasecmp(rfc_name.c_str(), "crc32")) { - return false; - } else if (!strcasecmp(rfc_name.c_str(), "crc32c")) { - return true; - } - return false; -} - - void trim(std::string &str) { - // Trim leading non-letters - while( str.size() && !isgraph(str[0]) ) str.erase(str.begin()); - - // Trim trailing non-letters - - while( str.size() && !isgraph(str[str.size()-1]) ) - str.resize (str.size () - 1); - + XrdOucUtils::trim(str); } @@ -1060,42 +948,6 @@ void XrdHttpReq::mapXrdErrorToHttpStatus() { } } -/** - * Select the checksum to be computed depending on the userDigest passed in parameter - * @param userDigest the digest request from the user (extracted from the Want-Digest header) - * @param selectedChecksum the checksum that will be performed - */ -void XrdHttpReq::selectChecksum(const std::string &userDigest, std::string & selectedChecksum) { - char * configChecksumList = XrdHttpProtocol::xrd_cslist; - selectedChecksum = "unknown"; - if(configChecksumList != nullptr) { - //The env variable is set, some checksums have been configured - std::vector userDigestsVec; - XrdOucTUtils::splitString(userDigestsVec,userDigest,","); - std::vector configChecksums; - XrdOucTUtils::splitString(configChecksums,configChecksumList,","); - selectedChecksum = configChecksums[0]; - auto configChecksumItor = configChecksums.end(); - std::find_if(userDigestsVec.begin(), userDigestsVec.end(), [&configChecksums, &configChecksumItor](const std::string & userDigest){ - configChecksumItor = std::find_if(configChecksums.begin(),configChecksums.end(),[&userDigest](const std::string & configChecksum){ - std::string userDigestTrimmed = userDigest; - trim(userDigestTrimmed); - if(configChecksum.find(userDigestTrimmed) != std::string::npos) { - return true; - } - return false; - }); - return configChecksumItor != configChecksums.end(); - }); - //By default, the selected checksum is the first one of the configured checksum list. - // If the user gave a checksum that do not exist, then the checksum returned will be the default one - configChecksumItor = configChecksumItor != configChecksums.end() ? configChecksumItor : configChecksums.begin(); - std::vector checksumIdName; - XrdOucTUtils::splitString(checksumIdName,*configChecksumItor,":"); - selectedChecksum = checksumIdName[1]; - } -} - int XrdHttpReq::ProcessHTTPReq() { kXR_int32 l; @@ -1169,15 +1021,19 @@ int XrdHttpReq::ProcessHTTPReq() { const char *opaque = strchr(resourceplusopaque.c_str(), '?'); // Note that doChksum requires that the memory stays alive until the callback is invoked. m_resource_with_digest = resourceplusopaque; - std::string selectedChecksum; - selectChecksum(m_req_digest,selectedChecksum); - m_req_digest = convert_digest_name(selectedChecksum).c_str(); + + 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; + } if (!opaque) { m_resource_with_digest += "?cks.type="; - m_resource_with_digest += m_req_digest.c_str(); + m_resource_with_digest += m_req_cksum->getXRootDConfigDigestName().c_str(); } else { m_resource_with_digest += "&cks.type="; - m_resource_with_digest += m_req_digest.c_str(); + m_resource_with_digest += m_req_cksum->getXRootDConfigDigestName().c_str(); } if (prot->doChksum(m_resource_with_digest) < 0) { // In this case, the Want-Digest header was set and PostProcess gave the go-ahead to do a checksum. @@ -1316,13 +1172,19 @@ int XrdHttpReq::ProcessHTTPReq() { // 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 += convert_digest_name(m_req_digest); + m_resource_with_digest += m_req_cksum->getXRootDConfigDigestName().c_str(); } else { m_resource_with_digest += "?cks.type="; - m_resource_with_digest += convert_digest_name(m_req_digest); + 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); @@ -1912,9 +1774,8 @@ XrdHttpReq::PostProcessChecksum(std::string &digest_header) { TRACEI(REQ, "Checksum for HEAD " << resource.c_str() << " " << reinterpret_cast(iovP[0].iov_base) << "=" << reinterpret_cast(iovP[iovN-1].iov_base)); - std::string response_name = convert_xrootd_to_rfc_name(reinterpret_cast(iovP[0].iov_base)); - bool convert_to_base64 = needs_base64_padding(convert_digest_rfc_name(m_req_digest)); + bool convert_to_base64 = m_req_cksum->needsBase64Padding(); char *digest_value = reinterpret_cast(iovP[iovN-1].iov_base); if (convert_to_base64) { size_t digest_length = strlen(digest_value); @@ -1932,7 +1793,7 @@ XrdHttpReq::PostProcessChecksum(std::string &digest_header) { } digest_header = "Digest: "; - digest_header += convert_digest_rfc_name(m_req_digest); + digest_header += m_req_cksum->getHttpName(); digest_header += "="; digest_header += digest_value; if (convert_to_base64) {free(digest_value);} @@ -2978,6 +2839,8 @@ void XrdHttpReq::reset() { // Reset the state of the request's digest request. m_req_digest.clear(); m_digest_header.clear(); + m_req_cksum = nullptr; + m_resource_with_digest = ""; headerok = false; diff --git a/src/XrdHttp/XrdHttpReq.hh b/src/XrdHttp/XrdHttpReq.hh index f6dbdf9c5af..81e50683eea 100644 --- a/src/XrdHttp/XrdHttpReq.hh +++ b/src/XrdHttp/XrdHttpReq.hh @@ -43,6 +43,7 @@ #include "XProtocol/XProtocol.hh" #include "XrdXrootd/XrdXrootdBridge.hh" +#include "XrdHttpChecksumHandler.hh" #include #include @@ -125,15 +126,22 @@ private: // Sanitize the resource from http[s]://[host]/ questionable prefix void sanitizeResourcePfx(); - /** - * Select the checksum to be computed depending on the userDigest passed in parameter - * @param userDigest the digest request from the user (extracted from the Want-Digest header) - * @param selectedChecksum the checksum that will be performed + /** + * Extract a comma separated list of checksums+metadata into a vector + * @param checksumList the list like "0:sha1, 1:adler32, 2:md5" + * @param extractedChecksum the vector with the elements {0:sha,1:adler32,2:md5} */ - void selectChecksum(const std::string & userDigest, std::string & selectedChecksum); + static void extractChecksumFromList(const std::string & checksumList, std::vector & extractedChecksum); -public: + /** + * Determine the XRootD-compliant checksum algorithm from the user digest string + * @param userDigest the string containing the digest names. e.g: adler32, md5;q=0.4, md5 + * @param xrootdChecksums the vector that will contain the corresponding xrootd-compliant names + * These xrootd-compliant names are located in the static XrdOucString convert_digest_name(const std::string &rfc_name_multiple) function + */ + static void determineXRootDChecksumFromUserDigest(const std::string & userDigest, std::vector & xrootdChecksums); +public: XrdHttpReq(XrdHttpProtocol *protinstance) : keepalive(true) { prot = protinstance; @@ -236,6 +244,10 @@ public: /// The requested digest type std::string m_req_digest; + + /// The checksum that was ran for this request + XrdHttpChecksumHandler::XrdHttpChecksumRawPtr m_req_cksum = nullptr; + /// The checksum algorithm is specified as part of the opaque data in the URL. /// Hence, when a digest is generated to satisfy a request, we cache the tweaked /// URL in this data member. @@ -413,10 +425,6 @@ public: const char *hname //!< the destination host ); - - - - }; diff --git a/src/XrdOuc/XrdOucUtils.cc b/src/XrdOuc/XrdOucUtils.cc index c3b7e7f1b72..bac187722d2 100644 --- a/src/XrdOuc/XrdOucUtils.cc +++ b/src/XrdOuc/XrdOucUtils.cc @@ -1400,5 +1400,14 @@ int XrdOucUtils::getModificationTime(const char *path, time_t &modificationTime) return statRet; } +void XrdOucUtils::trim(std::string &str) { + // Trim leading non-letters + while( str.size() && !isgraph(str[0]) ) str.erase(str.begin()); + + // Trim trailing non-letters + + while( str.size() && !isgraph(str[str.size()-1]) ) + str.resize (str.size () - 1); +} #endif diff --git a/src/XrdOuc/XrdOucUtils.hh b/src/XrdOuc/XrdOucUtils.hh index 96c1a3a1408..912f0f13855 100644 --- a/src/XrdOuc/XrdOucUtils.hh +++ b/src/XrdOuc/XrdOucUtils.hh @@ -131,6 +131,8 @@ static bool PidFile(XrdSysError &eDest, const char *path); static int getModificationTime(const char * path, time_t & modificationTime); +static void trim(std::string & str); + XrdOucUtils() {} ~XrdOucUtils() {} }; From 7a4db0397e0b4833012ee13a89093ab3b3ac0dc9 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Fri, 10 Mar 2023 09:23:54 +0100 Subject: [PATCH 247/773] [XrdHttpTests] Created unit tests for the XrdHttp checksum algorithms name matching --- tests/CMakeLists.txt | 1 + tests/XrdHttpTests/CMakeLists.txt | 10 ++ tests/XrdHttpTests/XrdHttpTests.cc | 167 +++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 tests/XrdHttpTests/CMakeLists.txt create mode 100644 tests/XrdHttpTests/XrdHttpTests.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4a9b7923676..d26a902851f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,6 @@ include(GoogleTest) add_subdirectory( XrdCl ) +add_subdirectory(XrdHttpTests) add_subdirectory( common ) add_subdirectory( XrdClTests ) diff --git a/tests/XrdHttpTests/CMakeLists.txt b/tests/XrdHttpTests/CMakeLists.txt new file mode 100644 index 00000000000..23bd22f867a --- /dev/null +++ b/tests/XrdHttpTests/CMakeLists.txt @@ -0,0 +1,10 @@ +if(NOT BUILD_HTTP) + return() +endif() + +add_executable(xrdhttp-unit-tests XrdHttpTests.cc) + +target_link_libraries(xrdhttp-unit-tests XrdHttpUtils GTest::GTest GTest::Main) +target_include_directories(xrdhttp-unit-tests PRIVATE ${CMAKE_SOURCE_DIR}/src) + +gtest_discover_tests(xrdhttp-unit-tests) diff --git a/tests/XrdHttpTests/XrdHttpTests.cc b/tests/XrdHttpTests/XrdHttpTests.cc new file mode 100644 index 00000000000..739b45b7f08 --- /dev/null +++ b/tests/XrdHttpTests/XrdHttpTests.cc @@ -0,0 +1,167 @@ +#undef NDEBUG + +#include "XrdHttp/XrdHttpReq.hh" +#include "XrdHttp/XrdHttpProtocol.hh" +#include "XrdHttp/XrdHttpChecksumHandler.hh" +#include +#include +#include + + + +using namespace testing; + +class XrdHttpTests : public Test {}; + +TEST(XrdHttpTests, checksumHandlerTests) { + { + XrdHttpChecksumHandlerImpl handler; + handler.configure("0:sha512,1:crc32"); + auto configuredChecksum = handler.getConfiguredChecksums(); + ASSERT_EQ(2, configuredChecksum.size()); + ASSERT_EQ("sha512", configuredChecksum[0]->getXRootDConfigDigestName()); + ASSERT_EQ("crc32", configuredChecksum[1]->getXRootDConfigDigestName()); + } + { + XrdHttpChecksumHandlerImpl handler; + handler.configure("0:sha512,1:crc32,2:does_not_exist"); + auto configuredChecksum = handler.getConfiguredChecksums(); + auto incompatibleChecksums = handler.getNonIANAConfiguredCksums(); + ASSERT_EQ(1,incompatibleChecksums.size()); + ASSERT_EQ("does_not_exist",incompatibleChecksums[0]); + ASSERT_EQ(2,configuredChecksum.size()); + } +} + +TEST(XrdHttpTests, checksumHandlerSelectionTest) { + { + //one algorithm, HTTP-IANA compatible + std::string reqDigest = "adler32"; + const char *configChecksumList = "0:adler32"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("adler32",cksumToRun->getXRootDConfigDigestName()); + } + { + //sha-512, same as sha512, it is HTTP-IANA compatible + std::string reqDigest = "sha-512"; + const char *configChecksumList = "0:sha-512"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("sha-512",cksumToRun->getXRootDConfigDigestName()); + ASSERT_EQ("sha-512",cksumToRun->getHttpName()); + ASSERT_EQ(true,cksumToRun->needsBase64Padding()); + } + { + //UNIXCksum + std::string reqDigest = "UNIXcksum"; + { + const char *configChecksumList = "0:cksum"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("cksum",cksumToRun->getXRootDConfigDigestName()); + ASSERT_EQ("UNIXcksum",cksumToRun->getHttpName()); + } + { + const char *configChecksumList = "0:unixcksum"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("unixcksum",cksumToRun->getXRootDConfigDigestName()); + ASSERT_EQ("UNIXcksum",cksumToRun->getHttpName()); + ASSERT_FALSE(cksumToRun->needsBase64Padding()); + } + } + { + //Multiple HTTP-IANA compatible checksum configured, the + //checksum returned should be the first appearing in the reqDigest + std::string reqDigest = "crc32,adler32"; + const char *configChecksumList = "0:adler32,1:crc32"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("crc32",cksumToRun->getXRootDConfigDigestName()); + } + { + // If the requested digest does not exist, the first configured HTTP-IANA + // compatible checksum will be ran + std::string reqDigest = "DOES_NOT_EXIST"; + const char *configChecksumList = "0:does_not_exist_algo,1:crc32,2:adler32"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("crc32",cksumToRun->getXRootDConfigDigestName()); + } + { + // If the requested digest contains at least one HTTP-IANA compatible + // digest, then the HTTP-IANA compatible digest will be returned + std::string reqDigest = "DOES_NOT_EXIST , crc32"; + const char *configChecksumList = "0:adler32,1:crc32"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("crc32",cksumToRun->getXRootDConfigDigestName()); + } + { + //Ensure weighted digest (;q=xx) are discarded but still allows to get the correct algorithm + //depending on the order of submission + std::string reqDigest = "crc32;q=0.1,adler32;q=0.5"; + const char *configChecksumList = "0:adler32,1:crc32"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("crc32",cksumToRun->getXRootDConfigDigestName()); + } + { + //sha-* algorithms + std::string reqDigest = "SHA-512"; + const char *configChecksumList = "0:crc32,1:sha512,2:sha-256"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + { + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("sha512",cksumToRun->getXRootDConfigDigestName()); + } + { + reqDigest = "sha512"; + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("crc32",cksumToRun->getXRootDConfigDigestName()); + ASSERT_FALSE(cksumToRun->needsBase64Padding()); + } + { + reqDigest = "sha-256"; + auto cksumToRun = handler.getChecksumToRun(reqDigest); + ASSERT_EQ("sha-256",cksumToRun->getXRootDConfigDigestName()); + ASSERT_EQ("sha-256",cksumToRun->getHttpName()); + ASSERT_TRUE(cksumToRun->needsBase64Padding()); + } + } + { + //one sha-512 HTTP configured algorithm + std::string reqDigest = "SHA-512"; + const char *configChecksumList = "0:my_custom_sha512,1:second_custom_sha512,2:sha-512"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + ASSERT_EQ("sha-512", handler.getChecksumToRun(reqDigest)->getXRootDConfigDigestName()); + ASSERT_EQ("sha-512", handler.getChecksumToRun("adler32")->getXRootDConfigDigestName()); + } + { + //algorithm configured but none is compatible with HTTP + std::string reqDigest = "SHA-512"; + const char *configChecksumList = "0:my_custom_sha512,1:second_custom_sha512"; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + ASSERT_EQ(nullptr, handler.getChecksumToRun(reqDigest)); + } + { + // no algorithm configured, should always return a nullptr + std::string reqDigest = "SHA-512"; + const char *configChecksumList = nullptr; + XrdHttpChecksumHandlerImpl handler; + handler.configure(configChecksumList); + ASSERT_EQ(nullptr, handler.getChecksumToRun(reqDigest)); + } +} \ No newline at end of file From 53a4dd3cd2f35cb3710ea2334dc22815ad59a983 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 13 Mar 2023 17:13:35 +0100 Subject: [PATCH 248/773] [XrdHttp] Added cksum documentation to be put in the official XRootD documentation --- src/XrdHttp/README-CKSUM.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/XrdHttp/README-CKSUM.md diff --git a/src/XrdHttp/README-CKSUM.md b/src/XrdHttp/README-CKSUM.md new file mode 100644 index 00000000000..a48cde57f6d --- /dev/null +++ b/src/XrdHttp/README-CKSUM.md @@ -0,0 +1,14 @@ +In the case XrdHttp has been configured, it is important that the configuration contains at least one digest algorithm registered in the IANA database: https://www.iana.org/assignments/http-dig-alg/http-dig-alg.xhtml +Otherwise the user will get a 403 error for each checksum request they will do. + +Here is a table summarizing the IANA-compliant checksum names: + +| Digest algorithm configured | HTTP digest | Will be base64 encoded | +|-----------------------------|-------------|------------------------| +| md5 | md5 | true | +| adler32 | adler32 | false | +| sha1 | sha | true | +| sha256 | sha-256 | true | +| sha512 | sha-512 | true | +| cksum | UNIXcksum | false | +| crc32c | crc32c | true | \ No newline at end of file From c91b1acf4fcf7f39c7b104353bcde1cc6fdd3032 Mon Sep 17 00:00:00 2001 From: Elvin Sindrilaru Date: Mon, 27 Mar 2023 15:17:59 +0200 Subject: [PATCH 249/773] [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: Wed, 29 Mar 2023 18:09:42 +0200 Subject: [PATCH 250/773] [XrdCl] Avoid segv caused by reconnect vs force disconnect. --- src/XrdCl/XrdClChannel.cc | 9 +++++++++ src/XrdCl/XrdClChannel.hh | 5 +++++ src/XrdCl/XrdClPostMaster.cc | 19 ++++++++++++++++++- src/XrdCl/XrdClPostMaster.hh | 5 +++++ src/XrdCl/XrdClStream.cc | 25 ++++++++++++++----------- 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/XrdCl/XrdClChannel.cc b/src/XrdCl/XrdClChannel.cc index d2be5cd704f..5f6b0efa3b9 100644 --- a/src/XrdCl/XrdClChannel.cc +++ b/src/XrdCl/XrdClChannel.cc @@ -170,6 +170,15 @@ namespace XrdCl return Status(); } + //---------------------------------------------------------------------------- + // Force reconnect + //---------------------------------------------------------------------------- + Status Channel::ForceReconnect() + { + pStream->ForceConnect(); + return Status(); + } + //------------------------------------------------------------------------ // Get the number of connected data streams //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClChannel.hh b/src/XrdCl/XrdClChannel.hh index 0bd9474822e..d1668f04677 100644 --- a/src/XrdCl/XrdClChannel.hh +++ b/src/XrdCl/XrdClChannel.hh @@ -125,6 +125,11 @@ namespace XrdCl //------------------------------------------------------------------------ Status ForceDisconnect(); + //------------------------------------------------------------------------ + //! Force reconnect + //------------------------------------------------------------------------ + Status ForceReconnect(); + //------------------------------------------------------------------------ //! Get the number of connected data streams //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClPostMaster.cc b/src/XrdCl/XrdClPostMaster.cc index 15facb33878..7b860c3c18c 100644 --- a/src/XrdCl/XrdClPostMaster.cc +++ b/src/XrdCl/XrdClPostMaster.cc @@ -245,7 +245,11 @@ namespace XrdCl AnyObject &result ) { XrdSysRWLockHelper scopedLock( pImpl->pDisconnectLock ); - Channel *channel = GetChannel( url ); + PostMasterImpl::ChannelMap::iterator it = + pImpl->pChannelMap.find( url.GetChannelId() ); + if( it == pImpl->pChannelMap.end() ) + return Status( stError, errInvalidOp ); + Channel *channel = it->second; if( !channel ) return Status( stError, errNotSupported ); @@ -320,6 +324,19 @@ namespace XrdCl return Status(); } + Status PostMaster::ForceReconnect( const URL &url ) + { + XrdSysRWLockHelper scopedLock( pImpl->pDisconnectLock, false ); + PostMasterImpl::ChannelMap::iterator it = + pImpl->pChannelMap.find( url.GetChannelId() ); + + if( it == pImpl->pChannelMap.end() ) + return Status( stError, errInvalidOp ); + + it->second->ForceReconnect(); + return Status(); + } + //------------------------------------------------------------------------ // Get the number of connected data streams //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClPostMaster.hh b/src/XrdCl/XrdClPostMaster.hh index 89050ca195a..3ba59e2759a 100644 --- a/src/XrdCl/XrdClPostMaster.hh +++ b/src/XrdCl/XrdClPostMaster.hh @@ -152,6 +152,11 @@ namespace XrdCl //------------------------------------------------------------------------ Status ForceDisconnect( const URL &url ); + //------------------------------------------------------------------------ + //! Reconnect the channel + //------------------------------------------------------------------------ + Status ForceReconnect( const URL &url ); + //------------------------------------------------------------------------ //! Get the number of connected data streams //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClStream.cc b/src/XrdCl/XrdClStream.cc index cb64cbb6bc6..b3c4466da35 100644 --- a/src/XrdCl/XrdClStream.cc +++ b/src/XrdCl/XrdClStream.cc @@ -347,11 +347,14 @@ namespace XrdCl void Stream::ForceConnect() { XrdSysMutexHelper scopedLock( pMutex ); - pSubStreams[0]->status = Socket::Disconnected; - XrdCl::PathID path( 0, 0 ); - XrdCl::XRootDStatus st = EnableLink( path ); - if( !st.IsOK() ) - OnConnectError( 0, st ); + if( pSubStreams[0]->status == Socket::Connecting ) + { + pSubStreams[0]->status = Socket::Disconnected; + XrdCl::PathID path( 0, 0 ); + XrdCl::XRootDStatus st = EnableLink( path ); + if( !st.IsOK() ) + OnConnectError( 0, st ); + } } //---------------------------------------------------------------------------- @@ -399,11 +402,11 @@ namespace //------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------ - StreamConnectorTask( XrdCl::Stream *stream ): - pStream( stream ) + StreamConnectorTask( const XrdCl::URL &url, const std::string &n ): + url( url ) { std::string name = "StreamConnectorTask for "; - name += stream->GetName(); + name += n; SetName( name ); } @@ -412,12 +415,12 @@ namespace //------------------------------------------------------------------------ time_t Run( time_t ) { - pStream->ForceConnect(); + XrdCl::DefaultEnv::GetPostMaster()->ForceReconnect( url ); return 0; } private: - XrdCl::Stream *pStream; + XrdCl::URL url; }; } @@ -775,7 +778,7 @@ namespace XrdCl log->Info( PostMasterMsg, "[%s] Attempting reconnection in %d " "seconds.", pStreamName.c_str(), pConnectionWindow-elapsed ); - Task *task = new ::StreamConnectorTask( this ); + Task *task = new ::StreamConnectorTask( *pUrl, pStreamName ); pTaskManager->RegisterTask( task, pConnectionInitTime+pConnectionWindow ); return; } From 9d52adea782d083e3b26d347f2467efe7005d9b6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 20 Mar 2023 14:22:28 +0100 Subject: [PATCH 251/773] [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 252/773] [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 253/773] 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 254/773] [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 255/773] 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 256/773] 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 257/773] 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 258/773] 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 259/773] 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 260/773] [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 261/773] [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 262/773] [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 263/773] [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 264/773] 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 265/773] 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 266/773] [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 267/773] [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 268/773] [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 269/773] [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 270/773] [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 271/773] 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 272/773] 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 273/773] [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 274/773] 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 275/773] [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 276/773] [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 277/773] [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 278/773] [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 279/773] [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 280/773] 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 281/773] [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 282/773] [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 283/773] [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 284/773] [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 285/773] [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 286/773] [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 287/773] [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 288/773] [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 289/773] 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 290/773] 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 291/773] [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 292/773] [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 293/773] [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 294/773] [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 295/773] [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 296/773] [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 297/773] [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 298/773] [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 299/773] [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 300/773] 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 301/773] 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 302/773] [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 303/773] 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 304/773] [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 305/773] 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 306/773] 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 307/773] 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 308/773] [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 309/773] 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 310/773] 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 311/773] [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 312/773] 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 313/773] [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 314/773] [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 315/773] [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 316/773] [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 317/773] 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 318/773] 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 319/773] [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 320/773] [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 321/773] 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 322/773] [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 323/773] [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 324/773] 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 325/773] [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 326/773] 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 327/773] [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 328/773] [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 329/773] [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 330/773] [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 331/773] [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 332/773] [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 333/773] 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 334/773] [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 335/773] [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 336/773] [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 337/773] 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 338/773] [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 339/773] 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 340/773] [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 341/773] [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 342/773] [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 343/773] [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 344/773] [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 345/773] [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 346/773] [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 347/773] 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 348/773] [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 349/773] [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 350/773] [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 351/773] [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 352/773] [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 353/773] [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 354/773] [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 355/773] 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 356/773] [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 357/773] 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 358/773] [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 359/773] 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 360/773] [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 361/773] [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 362/773] [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 363/773] [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 364/773] [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 365/773] [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 366/773] 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 367/773] [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 368/773] 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 369/773] [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 370/773] 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 371/773] [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 372/773] [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 373/773] [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 374/773] [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 375/773] [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 376/773] [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 377/773] [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 378/773] [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 379/773] [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 380/773] [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 381/773] [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 382/773] [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 383/773] [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 384/773] [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 385/773] [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 386/773] [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 387/773] [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 388/773] [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 389/773] [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 390/773] 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 391/773] 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 392/773] [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 393/773] [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 394/773] [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 395/773] [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 396/773] [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 397/773] 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 398/773] [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 399/773] [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 400/773] [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 401/773] [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 402/773] [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 403/773] [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 404/773] [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 405/773] [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 406/773] 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 407/773] 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 408/773] 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 409/773] [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 410/773] [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 411/773] 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 412/773] [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 413/773] [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 414/773] 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 415/773] [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 416/773] [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 417/773] 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 418/773] 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 419/773] 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 420/773] [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 421/773] [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 422/773] [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 423/773] [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 424/773] [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 425/773] [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 426/773] 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 427/773] [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 428/773] [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 429/773] [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 430/773] [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 431/773] [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 432/773] [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 433/773] [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 434/773] 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 435/773] 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 436/773] [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 437/773] [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 438/773] [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 439/773] 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 440/773] [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 441/773] [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 442/773] [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 443/773] [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 444/773] 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 445/773] 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 446/773] [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 447/773] 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 448/773] [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 449/773] [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 450/773] 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 451/773] [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 452/773] [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 453/773] [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 454/773] [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 455/773] [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 456/773] 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 457/773] [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 458/773] [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 459/773] [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 460/773] [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 461/773] [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 462/773] [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 463/773] [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 464/773] [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 465/773] 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 466/773] [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 467/773] [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 468/773] [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 469/773] [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 470/773] 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 471/773] 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 472/773] 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 473/773] [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 474/773] [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 475/773] [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 476/773] 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 477/773] [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 478/773] 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 479/773] 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 480/773] 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 481/773] [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 482/773] [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 483/773] [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 484/773] [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 485/773] [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 486/773] [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 487/773] [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 488/773] [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 489/773] [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 490/773] [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 491/773] [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 492/773] [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 493/773] [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 494/773] [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 495/773] [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 zcmV(tKH9Z&&Z* zpQ~##uCDEL=r7Z1-w&rX9{^MIrsd|eX~ugevg0eQabynpU&;j zPV^nk=G1@5b6Nb^HBWOG->&{bzoci!rroVqKc|Vl9%x;wWBYuV9&`ai!X)rv@w+Pq z#OLe$&t_WX8PKE8eZS+M(}TPAOS_Y;!NaTl(4Tk>vdS>*wmBY)hyU8#*0z_nE&1Q_ z>Q{Z2R_9|TV&85M!*E+2>4WBc z&bR8DmS(^Cfv(}BErj1pOR-lM2d!h!RZGq2)XmqRr2V@~=exhbd-AQ%|9&2taa_wb z44c)hO~)O5J*KpCy5A3%JpMGKM+4_m~YzQZz9R*&>vKj6x7EL@;LI>EpLCOE=;r@<(p^t z+eT$X}d@O!F^Y9`GfXjqcBRSBu>~2-|lKD(VeW$Mw+U z)8@D2^%6^M7v@7+2ptJaq+(%_x%kqQ2joZFAutnu$2Gqp$+G?pUO``PK%_XBtZ|La zEf2X~eJA{Ne>w8|PM717#|}hn~A8p z=Kt>b`(9qgJo6V~NgbV5zLTr=)I298LHgj>z9hf<#*Gj8Zmf3wsqNZ4C*vslc0B0v zGi|I(egpcDZ<}_gzK!RD4;vvlVQTg{Z%}!JukKV{{HMOS!m?u8yZ+kgs)b_lms%0} z3u3+P=`sJ34vua_!au|Jv@v(@Src}j6*~U>EB9_l=VQJv&arwMm~;B+BTO_L_gGe9aLs>@70mjfD7;(J|9vZt&y#M z&YXB?rhLQ4QXS-tF2qizD=hrRyDnqfg`sW0BOF1S2ri;FuQS?eCt89w8 zQo5%bmKUGtGAS*!-SWK!0pIyuCh_Y7sJoaJ?O)gDOGJAMdMDks$#1A{Q|Yat-!$O$ z#BNoCdCrPgM*{J?i&{QcQu!`tdbk~f;u}VV-lIN=d}&IDl8vd?#^z3bEfF~Cv${A> z;oph=K}U3;^^tTS`snhd7}_IWDgK&%NF3r(sH6kQoyb)q+O&!|=RUN+;dcsQ#QOMtPf@433iw9=b=nk#F^?qp$25kT)*x$FFTRi63lx zXiRX2ZGSp9nF)!e%k+1YJO81{YiLxIxFC7Xoc+-Wf&Szdl4%RMp-xKs$!3?1$M$lv z&Fj0{@7Yl8yA((&PF-AGymNVkAKL@3Ab*9$V-+j{Ut7cvSxoikwz=~fc3;?AKD4`C zdP&EHt$Ob9f*zF3cbIA8{W#tRdV!8FHmD8y!{vO)+%=6$w`c167rhRAx^zFXM##tO zlo6?BH;|W>{8OwIyks39|G6}N-kdzLIz90cwak9m$E#CN`HFn!WZ5Qgt(3Ca1+@UY zP4_PR?EWQK^H`)3O<u%`wCxS4LdSDfgJ5`)9dx?Ol$JR1f`$ii6lGPV z-aQJRX2TtJ5H$JsyXHjaoApa_3qa;yG5@XTHz(HA=*zLEvH3URPhE5$NHl?%irog; zXjvzQcqTQGz8q-=^txzA32DBrrq1`^akb%)j4Z8?sC#VbEBjv(Hp}b=p_ zq?tl`(;L*QO5dN=qDg$|$nQDDAz6p`dw&%%GMq{5 z(h^Tt%4L%kHo5{d(h`l{J5bh~-wQoOYgH|LE=o)_0HVzH`dLh}BzU zE$h{92~I~Dt*}g$FOTKOCF7nw?Pl(4iVtr@UfunbQ4FF>E$D*3ywPpD(BS#2BQf>- zNy)wSjPC4dc#AXtyr92j-hc=w(Fn;OGmccA+!E#`_ZAcccfqhqS+E8A%dc16>K1Mc zTwJ?+y=Zh|p-bAG+Cz3^R~I&FJgIR}?oe zZX>SnOP72zHs)aV*x;h+hbM3CvMh zBS}TQcd#V!OQP*@k}CshKFJLM)d$@lbK%9#iM-G$XKd*=F%PB#{-d)#(5iR&UcOt+ zIXO>Px!oo`Us^ix)1RM&NrLapEfimlS&80Fw6eVnP?Zp%sx>+O^uEF-o)G%-c)W=g z&KuneXCc2s_?&pIy5A`F$^+hDUQ6&*lx{M0Qg>=68hyGZ=A_B2#HcCR_cw`)gCYm< zy}Hvib3SYcID>H`vmfMpI=1{KV&eZ@9BsT!m?MI6dEpj*!VLO>JP$nfEmLu30%!7{ zJ<^qvZ;Q8jNNIa$kJ*4Tx5!QSy2~BR=cXLlby$2~gkDvQy0u`_pOXeL2a34_LJBPN z78EKxzHo2zAFL_V7uSu-&v%VIp+fPcivh(hxxAs7Ys9^)$O%K-C8|f#Rr{gipto#m zJ7yABA>Woqz^hM>HF1cxFn1;9H#~{E_iAd_jXibMRLPx2+A4bm^M^yqR#)eH&MSY! z(na59H9!V|CeF4oZkAqSJ{k;D-#C6nG#WIaMkLsqk?p)6zT`#ERDWWrP~J5z!WN_6 z4VH(tv#!g7VHIo&YRR_G#wZOnX2I)r)pU@U)qTTV<2%aa$#ax4r9l-Fqvt}Lt!IU= z(8pMK=Z{6t6jvjkIPSY$CfZ_NQRT|;_`a!1N6Lnv!yp#<5Urwe0mReEf57iE6`^$A ztQQ={{OpyzebE}@=pxbkKrFg9WJTHZzDHP7Ux$1wD=Y)FHE-*1SgV?y~~tmAZ#$f?h7)(}8$O zTH$if&t#2rOYS!Mx;?=m3-@c@o3o^sH8Px<^GkM?95&qbdrpQkmJrrP(_X_*K;&5u zNdDYo{^_Wmi~X_RzQiES-VGl2NP*sUpBC`k-2oJk;P3&fTrR|VlLrze1!$9rLYS@UqQM zL%jJI1}Y&B^DcTt=o*7L=Pi6#y+7qYPxfS%Lwp(E%}ffUSgE*U6?&$j_$ZrWkH97l z26H^6z;xpkrnwjqi4sx=;v`IqI2f#0sFtV9a-l2?3A}4@4l>jY;w9?|za7+l4z?mj zpvL1&PbC5a%V&9kRDwWI&}=iC?VgcGyV_r#+li*Y-1|#D(M>OoHd)#xEoDZ54od5R z4tKr!c+29EBIcZgJz6FrTw__mLkO@LD4uCz*{qBK`2#1FN<{TBTQi0*ljj24UYX)_ z%x}WAF|*UXFLxImm-(m{L|l&jj5D)1W3wp#xo@-93ZqbdG9L5HPw77O!}4~qPdT>i zYNub)P<%b|hCC0#2 zPjl5QZ#+iiHfWGPEB=4@d}T8Kx^LHCQS1~<|8DcAZZ@S0gNZ<6+F&bEoTul z*;+lPMAS`Yfhet+7qp!xh2)2>#PKZ&v?0fHzQ+Fj;WCh`C+jNAqmA!@k?fpbWImR4 zhnuYmsY0A_9e7Hmg`15A+3$B1XVLIY3bznhAw&qD5+$;WZ+*>Mn13g6jplsrTV#8X zP1Z5#ayf(q$tWNW6&4OTURl;JJOS<#vXnWWQ^_^vN zI%8}HUJ3hyLu6w)2`dr-nXMd5%~Uvuzl^dSrW=<*s9TLV9JV~>t?Igjb}b(m8LW@5 z5RS=E_0h2NfCpfcA-|FhJo*LCKcOr@XDU@1DmpMjs_0GpW0IWxRM+eeRqM11JRkDgmYcWN_i(<=_duhzzoEv$|*?$iHieR%2e`us4KRwr}f`6|oA(_tDPo6L0c%_PY` z|5de0e|Tx7{o=*?=5@-8@0_G=;nG6Gv}CdE?;;(R$@Ql!0d@~}eq5~?&n_(Y9wYhY z^@bNOZ~&plub(o{Z+2IG3rI5yvy9hqtL&;6WOi$_#9d%d|JI`UMe{RWnC_hmyFGm4 zA+rvfvV!vve|!zD2E-{V61QLFd6SRt|Gs8zFuW5|JL!ydvP=+1;@N$ImiR%_1Ac1Ghh)B< zWjEo;RAsU|G>W+ZUJ#PfZS9(d6#`iK)mfPqXXq$ubHpt0A5$_K6xAXHWN#Ud_)Ifk zPi}Y7R@=GXR$HoSUh>bti#0MhgS*w5O*eufMiSaTXs zUfKCzNE${m#ZP?M=_~?(0o^{bE>9dM$|Gdee}+LJ2Yqd~nH#IEgN%zte1;1Z8Nu;J zCPUV3CX|n9(lw$Q7Q`U)niHY=juMT(w8TQXsqHTLByLXKL!z{Y6sVvsKjM0b*?dp} z(xxw9VJGtB+k2PkN`n8CfHL$quT>ChUoE-U9G*yw3i%7oIJpC8sZJE*^w*;$3*nw1 zN7|Ooj!UdsreYaVQh35F53KWfXgmifByG7#{7MlJjq~9AsagGtg3g+Nc}~Ng0!d;s zhTpE8WmOV9vOMNytPyav{$0-Y*{&4s4^3v<55wg=X@Sp=o>LbQV5qcMU;m};kK_yu zjfXTeud5wq~x=}^q_BgMog&c0cZGa8BfXL9gdQ7JR@{b`H=2L$_t zy7CV^^LJXE+X*S3c#T956_5vS#shZs+%vOp#shH*<^C2u>>PfErb~75yD~ooGZR*A+W=o6eik3) z5=Er4P}Y?Lq)<$;UW+EeC1bFoz$kDQI`;9{Unz{o)iSyWDrp&~J~O<9^Ad&^sAG*< zO#1&_FKIHAA3(t^O85~#nd+KQ7K+f55w91Wwo9dDYi5@WO~a*syaTg((zcSNe6*Z3 z{GE~zXcdJ0w8}GSHb>gq6NQAoP(XN^rT5@sSy>IvioVrtyR95Iq|ArX@7{=tV(-_& zko{6&bG+AUXxaAMNZN6X-3^?gT1?GzFUV19y3B;krK!lEuZqX>w0_x(ELp|^$TPW% z6=1WhD&eDeo1qUZFHZt;7S~)_m2v^*KY24e3aUG3RXq81NK2aq*BN0*DmW+0?W$|B z_nL>zlM&C2iceG-Q%)1{=SRv9=!YGn7K!$+u3$@kLu_#D@vFltF+}G0-y=GSd@eI6 zZmazFSkk5O{KPW;iG1wcY9P*(^3yII_?{FJm=9l0@R&G^tATZ)IcHh9>=~hS0;|nyr*>tW9*5=tf&MnJw9R04~mbfS-gRAU) znS|ys9T{9ClT9FEk8a*2MSB9&W`BJ2^)gQY|0ygQ5YJ zEq39mkz(Bdn{SW6*3~DbnW}#Hlwd+pPEqUMC>Vr+?V#57lufRK7?1E^Bm*{(x5g)E z+6YCVB0=sR*&P$*8o3jP3`CUZZH68()U)eatW2>T$naC4kR&kEL8H9ien-PTq~qRZ zORE~>40s-&Ebdm@9dre1ZzbQp{Ga1dc~=Fk$<_IM+t(7k zrld&R_rFQJknOGf9u(VamU%fRU)sr-PBol*ZP~IWuj|UdhUSJp{52vsDkZs;I+aZM z6IU+Yu@eVYIdg|`8x592^KWYV$+VoNfI?B&&2{uF-%(HKXQGNEb*|qZn@Q{68(K%K-?%LpEc*XX(Ch21Q0%{M*x zZ}KG4X;X=hx!t$Ov+c+hrs)7{&=(tsMNgSXI+AP3;VvG)W?~e*ewaP)O2)&II|_e3vnnsZ#n>n?S9!30^ORnf4&H2@?aWQ zJ&jPfJ2f{LfzwKp_SD7@`Hgy-a?@>d0`Ljzu6h^ntp==Y0(_W^?AZ27lrL1=*P;7E~PwA`&FQjc0l&$3d#V$MU6N?P0Kp z+>-eiS*?dnQKzxMK0dBIl1>01Sv^Eqw}BVW1F@TXD4<_+^k5VIeP2(O7(cjG zyIx#tZE=cS<4{KTwdl$M9@kO)l-#O_I%*k0rp)8i3}sEsqchol!GT(z8@`F>yN7DH z$$Vq7I=NBr1Kv0D51$fvctd5y&T9euM+@m$I0Ha7vf zQ#4p!+6D#xy8?8t0HoI11&1HIkv!c{j@%u}f4qmHKNmy+J_C-v*Tg)t;mU`~kv3HT z=(kTX!(lN3%)h*;TA+Uov9kt9l98O~>sJu>Ro-Jf^}~bz%O8vKeF3>$M|pJAUcBJB zuVmNyHN@z*1(eFy)?cD>xmS#U;qcDAGx5zW>YJq4uoLvL8f9WMBKujgyn&D%P|w%V z9%4n#gxG-YqfKAHMv%Fn2rwcXl#_nzff3oC2{&iTmU>KB%Bso}Wj;`%1rYVFh7NV? zFFSMA7J=l(XWaOFt0~q?zf+}(c-)g^Uf``Ww~rY?8_7PwR1g-9vn}y;)MFKCB5=w- zwWCqLo%y>}$SNT)X2+ZuY=j^hiv`8CXhkrVazw!;$*flUVLd?!wl#{;IUMsv!OZJZ z`O&~azP3IipFfey^%YuP8!z33`WU`(7VMsgu_=#?%xxnWjrT)c5BjO+VNFu` z`F+zh3I`do1wD1olLZ^|D>c~M_l(%ENpcW`hA^h#i=f2IKa~X?Acm~wS5KfG^gB`gHx-Ic9`KV@ zX7c+=Y!7+eTXI1t%xUV5&G4K&Y%KI9S!-6EEe_!E&%DL9HkLPlK1IWgsgCY8*Btft z-Mnr=Z2aYty8XSvNC8xRy=g$nkCrofd0?}vB=|r-ga*R=Y!FUmnsC@v3Vwu*{weBKVtxWG*@_T%dPc*xwN`GX(+M|-By6ZvINcePn! z#dz=I>IdTOs>^=P`aqg;W7KE@18H#a7QoisXew-1aBj-2ViLz^0__c%QwmzKDK`hjZXd7!$sJX zNr(F9^vlQ9r?hEQ;s}^L{qd>UO#1JCpm5(m&&Q*(B#y_AJrYsrkbg}(do{gIYku0( zK;>eGREid@f}nk>fKomr$#IuQ4umN*XqKIFhzCwm(4G<~c_L==gV?kDVs>x7Hcr%i ztj(#&5`rq-^(;QWYfwl}OZ{Oms(ruRDy-r6&9O@dFGd&n`zgyc_?muEV?u%XgL3eG z91j<{LFyL!d*%mUQiw4U7RTxz*~Z!!j8lGRqmAJHK>#DBzR0irsS>Tfkz61eQ_Jg^ zrnH#uvQzqc-(^abfB&2g%|`$Dd%pkE1*RZI_%&b2;WFyi-#6otzx@v}q)@a0_x!Ji zO#36bQ6r$npvkF{%@MAkX zH~ftK>_fiE#gB%U;_pvwSN`CA76`lIk$Aft zeR+XR0uhllxZ~K@x}rt>wInNl_QQrG74~v4ltokhhzpTECySF16DP9hAVhs42;;!4 zL;P@50UNLM`yq>~-<47PtY+-{p@}eGaWwksarNIK?mnoi=f?oX{IM~^O~=>!Lihbq zc=*xkduZteQqKl$P_&|ubmS2hIP0p6*`bIzzouh)epGjrRp(V9BNmB|=D%ISwdr3b z6BPW1%**<=$5R>M6EdWj&3PJ9%!}3xiDGwp$kPA(x5) zz2J_EsgiI~0Qig&G6eDON5bx2qv4{qY1|C$_C-~Jp<}%~6AcQ=sQ%JM1bz!W&7F3s ze~$!9juf;xG&`L>#rcOR4fTBL2|)Lte=sx{OD<}&Lv!V{xT!yjr>EH-6tLd2$>K^d z4qbc!P`kn$9LX^Yo0!Ghog7F%=Q>V@W84uij-;KKa-_1?CalHkk+q@%n{}+^YSV~& zdrtlN2#v`$SGHcjyCd-_4YWd8&?n(=>kSTZqGe7#3qQu%Wkh-arolP1u3J}kWw$ur#@_wogXMu4ly># zdc7DV^c*>+Jv+O}C;;i{QNE4PzHR7@)9?8vBT@^U+#!^0ijt#R?Sk#Wohij76j;i# z;$ppDEqM9-ntA>Sy@EceYcAwFT49K03_vofd^d22nnNn$5skQryIejy%my!}jd3}Ta-eigVFn_*=JfX*m34QxC__vM;#@iEM zFXAYlNh5xk|FD`59$QuU)j(lMe`Zzv{n~9qrNo zsjiFO;|KI9N)5#86FOz_*p)KJkUA4N1a8n}|J$wYLz8s|>l=vah#LfFTmq+*RumqV zDkX6IPkCmo2k!Ja3}w|!AKT-$k#s{n05+%x@}`!?u0E_o`ZrHmejOxf1Mg*Y$7Vp= z3re45>~PkSiUY=?c%_E;3KOcELUn}BDtidpUeediLC+h(W=q9aYBV%{6y%myS<$+~zI(J% zNcu-^w;nQk1xbC7Y$ii$ru4c&%K`#^v*>H6T^3JH!@D4{S!kR2s4zcKaVXR{)IxO(wJhO&RZFx-hJSr5-ybO7{47AE~E`JE5J zACzJeMP;(GnYc-sT*jI#)AKG_6W6gipL1{UvVaa%iU&=4PGw_U0M)$#X37b}OMHo> zCP2N51H1&cyTd@^pzZ4xv0`B?G?CRKX3E4 ztTQ1Y)Y7d+?!n^Cd`C)g^@AETAVl}u<3+yratnz};THf2{_uP|!}C4%cqxzMIkz$x z1824A3n4ko>0}wj?T`eCD1XyYGAU(-bu8ZW)naKMVtdN^YO||>+vGfjwu#m(tN}io zG{edRSmI65`{uP&CRz{X(>9&P<;7`II0NJx>NBJ1GYefpaRGpMPhOqPWeOjU%@@Ti z5+spNG}_bZJOH_w-zN(r+-`t}Yjfv`ljsu~eQ2k+sv{Mo&JEm4=8J-r61pJWJGp@J z=4{(hXzbA9er?hriCaR>fF`TGnmFke?Dc_H~7IZo+V$Fs2{2UD}xE+ zaZNmQFL|XKpmr#Eht4%SG*`7wG*bd?m*=b%Ebl1Smp!NQ8{}2c%Z#4iQMuF;$DG-B z#|KC!!HrR-UTzg%cfG`%J-hO18Q{Vg**votx;D3Z7eWpF2=8V<`HW@tH*bs8pr4c; zhN~Ht{K}M4aBitrSLH>n%OQQkIIhDippQgm@Anc{Yd~sbXQ{kY;m{tx=$7O^pB-)g zASfXcDi%Q#`h(&{7T8mvSt%MT1tOg?3MAiCQv}u)X&1pNvh*>uFf`SL{($&g z*VUXak~6R4BfuSHFz<*^`s)JrXP|9T7&O#?N*^O=HS|ifs51QJVW)LilnNCtFDEES zEAVD>l%}^0J3$L4G6k6ZO@@pE}KmMRkFeUbNFR77yJTBmWrmEDR)1>Mk6*}wz|*s*J*v3Aa^Pc$5|kZyW(IO{qSDBvF;4FYfSEQq2|#`^iWDokA( z3cE)*WR;2mGWloUh|bVqoxs$U9vq8HawF8km4&)~v$N&6j5J54q0rD>nP9Isi;ZBR z?#c_MNw)gV>alG*1tS|XT{b>cr^j}_^%`%vjl9c(7eRowEW|T|r7ZUPbOPqdQrxbz z-wf85r4HtfQPlyB&8pBaaP!Ga+;`BNh7_tW$o-KvgF08p{#9naYy-bucW#YWP6Oo_9g$M#Sp(};-YJ6D z*N=ZV9-EOq|32TNemFLi{TjYi=fvlB*Z5$POY!JX;G@FbH3O>v-=(4PmwT2Mf0?z8 zgMaNj53}6Z7a<9ZB8_Pi=7y{=P5q;#>lfdpV-q%5Sl|0HpXOh^M7LS=>-K#S^r%nm z4(W(RS>Z?y#xWuBEOb{CjjrCOz~4iH}N%?I7tzpHiz?fIl41>;(4`Bq=o`pU)7=31o8^gdtojw0k?X zyk^V9W3RR{mG^`WM!A;S+DIBuY1*cOq}bK{(EO49=)0*oHI;g( z4fi7D1u~Bxmx+z>n?T?3<5x+0^&d=&oL5WK{rI0Ql!dN0tdV*oW0Ah8UnM5}Vpguti=^_{oY-tsT)mWmH!=?;J4Cbo|cyay#EE=;u={uOft~YxNRKyi97-l8lXF!B~!XSif89Fi573s zI;Oh*FdnwO_=>~F?>p^o8<;XB&!k;~!QghJ%s=?i2&h0JO|kp4JFoJJCLo289Xy;= zMoZe0yOofK8XBKTJZoN8161&&uB-E97%!TS1lIP~CKMTIhGFKzWb;`6UAANoj*c59 zBEXdu-#*=Y--mwF90CXIRmBm5iMIH!nQ4vN%jgr+e$rIuRZ?(Zhx(8}0)_qkSrL#b zLYvrqNY)Cx0s0x|vV{v>@X5eJ$xD^QQDh}7%>IKn)gR?H`#gUwi@i>yzyiN2)fQK@VQpx&KoWmd}(|Zrbgsrxx`D zmgt=k4i+{H61=Wg;|s7f%6fI~$vZkSmb{?0v0*S+bG}Vk`c{n)8X+6dgBmJbI<*k* zVhLX`hmi%@FW<)EB^vmfuacYVHpamR$*Bskl{~ zO@v@@L#n{i!fs$PukER}!U=#Zm@n7$gN-yha{x9}y_p3#nXJ|eWZ~CTQD5Q=;RGb7 zRbt&J105%hpG3TAF-=aO>=8{!*Ng;=xi4-t3G|m(Gk&wf3v}T#$ zR1=0q?yupyOL*XiW1Ie99;W>H-Fym`5zPL3izYBSN{1|D4n;Dy8VPijW$~3v?Wq;r zwFLNjWy1@!4Q}scI69jo9D&AtL`d#n;R}liR#;5*JPFuMpH+y^W64LFODSR}3N1n@ zu#qGFOga+0DD3c2L|Jw%i)0%oUqWuVqMRDGrjVNiMqvavwDzi9D?oB zPnfHn+vn5=b*yaGZFM!GB#W*VjieJr+r%kH>qFtexlLQvoK>BZwDkv#N&_6*>q`#6 z8;Z&++xFB1!7BdYthhEx3@=Ai4OA^g15tzc6$#I>rk? zjC45JKw*(42<^@A0-ON<)vOl3kk|j!F=l836s1Yzi(M7(p+>QY2Ic->-<~4Yj*ctv z#v;6&EHHeDEs`hC7HIbSW45ArTZ+VFP{*T|ysM*8lgdyB8pgpZ*6Qv2*JbxStC}>7 zn;SDU>s`bF^7al9ExuP#&0lY>j-oQVj1TVkf#<}yg`UIYCG$y?2{_PL4fphHMl zjVr<24h+i!!n9CW{(1Yq7phV*=ZAV|cU&7V-NPyWg0iz$BL_M?#k>v&t;00$a>D z)7Cj14SK>7f4;oE%HzRP2zDwQ}$-CX#{ttPZ&mT1w_#luyqGd*wh&^1hFH)b`+- z*VX1TAz2|<^5=GB9!6hMIP3Mp{&-tmhd%Rxjn{3Z2Q^{%ipmkXt-0yPUZlY{&GYH* zBBlpa4yVD4{Us|((q)ko-RQK|c7 zsC7o6BTW9yxMCJ?<-}2vAY!DxljAbfP_?4^ci4f^27ID*hLEvVxDgpBvBkT$y!fDs z()Rs>-A6(|I*yS4WphG%KX?+XC7rY){xfgU0I0(oipUAUW6usKj3|v>a-VfDA(Q~_(FeyxS24d{wd zE)}`lzZ9j}@Gd209pEn+XjGoFJov*RlG1q~@DVSBTtIsQk83oSW|qSwyu-mKv8A7B zD}n7qadD)kpwu}KKU9f^G3v9Fls=;Bx0K?6{Bt--kie%2ceq!a95Px(X(GtFEZv8` zeO!gcqXD3g!&}&m??P`A7Bvvnl99+wyIm}Vw%uGO!?n@u%kiJD_55c^ain^V+kQxr zDDiu=!ob49BxI14^7^^o9{F9|f!m7}Xygw5Ejt>^R-Y%xFPX~kj{>;!BY{*R2f#Yy zY5ERd66ssJki8ILGw)_f)pwoH!DmS#yi^!c3L=yFnRoe^LR;Jl)pTTDklarbr&R)u zH>J_R=$lr8bs{IC11hBrlL9Xel% zD`w#x<&@#V`KI5kU0KxjSs{&I?e2A%`V-X3g;j{RJ1??8-`uDZ*Mk)$3Z@W-@K2^l zbZm^}@ny}(G*TE|SR^Jb(0KNh~9NSW>LQ=js&D-L&)R+=_6-nBZLU52y1!$wu!)CGa}^i`z6G*7#VY5W-eCDMrv~vrro~Z zRQ!mmB)uxYJj~EGtnvho&@9x~cYZe>QY8PAB?4F~ndNT?1d2V)qsP>{g9=#jRI-Rw zk8LKO6oLg+^Mp_ANR(6ENZiES!%Brq2pR8!lM;Kkw#RF&W(~DFM0O5^DG87JFXERF zwTbiErM}2gsQ-T@0j%ZF^ssH0njxM_n=KlJ#flxUE2Y0_whxidzk(vJQH;A{kM$#I zS`@gf`;obYqORAXmTFv(7vMVsbjMe-aJVZ5m`|ZLl*$=>A9S-v!ZDzXJ*}7uc(KnK zjeV-dFH4ifD^Tvtv1~t%4ZyxCKa6(_r{wLe;V{Q$<$ts%D>LlU@7yQIdB5Yd3!==vdN)2IVtHm%}@${`?xG=MDS z5(oIpFe;O0ey>y7j45hp)1DC3)b|A1n8SH zwFa(&MS9X?h!DlpbjPkC<13UFYZFcONq~iayh+YUyGIOLxE*`%_Gkj6D$>YFVaV`C zWz=<6;cBQ1l1=uo`U}+qHF--o2eg^kQ-$AXQ;&T@<%F=#W|Ynt`=rbejqvC*DeUkb zuG3HhI$$tJb$Xi42gy*9xZ9s7icdN-_md$xOnIALGGXd8x4iTimN-txyKqZZY%D!I z1SI>tg$P+b5;B<=!60pdzNuIOeGVtTl?CEJ&Cz>s1tK39Nd#KaZS}vGcKZV3pDC%K z_$P>XPyXNhK^8|Z9p(LK{RHI6HvcC;`5z+CoX&}e%|cI9dZj6=d3WhRv-sZAGJM9e^OLViK4M_xQV1l zF8C3t&Tq@b+AD(tY^BxJPCOCd%RGW(xMkYtSpP=iW`Tak&x#&xjS|wp^?T~A;w&!s z;I7T@X%~!9;b(|?T*%zdI@6JojW$S|!VRaUVSX-5nX4bD0%{m<6oJ`oL;cs`T zYw23)(w^)PGV6adM>Hx^_XBuZLEV2Rfykaas)R(%I7SE}nPlHUmaM9Z69PBuATNs| zI(H`@v?SY$3W&f4PE^c)^%gl=Dwp$=$Qc~*bZ zf`+E-(?dU7E}2=Vq-d?An*Hkb8>+l!R|qTsML2n+_DWiFi;O3P`B`$Hz!8ps$$&=z zn2?QCEzFwTEty*z3xGfcg0di7aL-wx@^U)G>J~_c*C{kXnO%r$T~o@*UPg-$aFj>0 zjPb9}W#YI3wk0YRzga{SK9txn%Tfis+a1@dW~%%(4b6^_pSaJJ6n%5+j`DY2JbQZx z@z>&QFoJr-!DRYecxsq6)wm$7M&I0a?<517rjU8AZeS83R)ItIAtVlWZay|&S*KQp z2(kNfocIhM(ionX%URFjL3oZJpX-@%a63$v}^R?YBYt4CU zLz%U2Z_8LIV?{1aH(rxrNe10UaeD&F2QEIV#bdU!UgqFWBK(MMQC|y=Nc|ok7Kuq< zZm2>8sA9sJswGE)iru|*3$Q{A0?qhKt*iN_@>>8ZK-IswO*yn()a`_vcpbh-UJYT} zY*5k-MBq5yue)p8=<5uqcSw~ajj!)XqoU>DN7704ZvO5s2V}o1#et5{+3H!b^^7d? zX+7MZrQm>W(w~?nEK1u~{C)~3gO#TIr>W`T)jbuU&~KZqohe@h{B&_^|Ll%3H@=8# zjWUAjS*I@2!M`!t)Ri-}SzQ_JhBBkrH#PP>5FEsAf)|ezhf8NZJh}6wnpJm~ADL~; z3;7ts_L)-Zr(VT1k}Lv%L-40OQ)QhB_6L7L^{lMLsd5;-9%j*%UJmp9Az^3c^LTuP z>ck?TEYk}-P^E*yyU&W5|EBOnPR%edzX?TiOHK@PED$^ZttvBo?bu80HYGlk&ug4H z5=@)Eiptk)NO4s^BpI=j&%vqo%+|5l`X^SH$OmmIpd4>#D=+CV9ixV{dhWqddPo*h zC)f1en^Kc)v|)yLL>&Yr)w%W(i>caF3Pxi=}=;5tci>bNl{#f zEAe!3^K-FN;fnAWw8VBGq1jdZzZWV}Rb=CdF&i1*&+u(5lvBF|RB7=FC0vWPGe8Tl zfsUnnv{f$REV*dO_ccMCma>PWO0vOl(l10EfkQI1hUTcyC43pw{^0j3TyT=Cv?k6C z4XhVk28kp~96H~1Lq)bqy)WF=n5Nya$A_5ZDb;VAuiX0L?g-*tHtd9qMXv6&@G0j_ zqKr&eJ@TIO+{tiztXy`?p6E2)?~||Mj>`t}^&k3vU%Py=jfkcIRG(ViN0slNnXM

&~OaH|cf!JxeWe0&j+_1KSNe}qJw$bLB|h3Rxk*=7wy94FSqinT;Vu9aVkY#z*r7Xe)ir5fh5 z8pbqy_1N4YG6Nr^n9z9VvQn}p$*OWA#pO#MG3?M^{wGaha)i$%fTb*)lrYZAMD!^s zI25({)W%bu-vCyre9SP|G%vQ?(!%8uN(AXdC=FT^D93=ka#HFjG-=uifb=@#pPbacitj4+=VPr`xJox#k08XSvh@SSF*I+FY@uWgbr;p}c2D z!Uwz}3Dh*9P$($cwW2~HrwE;miCmu0M1=0Qs~5~#IP-!eA2_J+@!h3+X6V4*vKE&!Rz4cGhdC#te^R9IW3$fdFKn6MWO5dQ@H1dy6XxXxRD44+I zeCDo=@?GgMuVY`k*m zgsBwQPL}$AymdS`cE$AjeL=@a5Nx>kl7bJJ%T7Z|^&aqpNi$w3(YGA$j`9dsX9Rjz z@Lj*h`32LV@WZgncePK}XVCH*r6aR@DPqz$FH@DXD0;%(h{Vev0&(CNPX%*YkYk?e z#H!_?J+oqIe$21&VNfVb1;r2JwJ#*Jz>-X`19HPQN`f8IGr`asB9v|NioiR0C@$O=! zVR%D_-UvWzy%l`@Um2grQN|Te#{5YVA@S zO~eR_1)6>MhMIkj?;}EUg)O;;2OH zJ?km)+-}=O0FKzsi7tq1EB#$eE;L~o6Q{sQ&k4-?QGM*uSIi&_5bVZ8_;*nau|&wT zAjK~*JkwazZh?Q^*vyHt@h-tuFuYuo+$T^l2*l#kv?HU60 zMlWG&t!EFMxk7|La?=G?&^%``{}P(eE^8YTn1tKVal||7=ltqUQX~Y$JM(@3eRD4n z%8)Z=*~uU@DWE=leNUDw_a}o$P{G(#a`EBcQ4vV!t%F7|kMrKw5gz`$j<8;xdujNC z?nti*@H`;(>_ZGf+W0kS-=gf@u%_TDCBd+1U`+;?Wg5@eTRT&SD)j)_JhBc|36v3f zhsy@P;XktwI79kHpi>l7MG)2UCnjYKEtMt~CkK9^?&0*@c3k?Cb(5z;D|yl_xnubS z6-3XXEffz(cvU=>&y)4mE5g(q@Uc3TyGqD7( zy-V98*?#R*z*NlqId;LxbY#xpc}A$#?BtRo)G6T>lg?sxwHC6bIJCqn!m3yYAM~@E zH(Xg*e*GW=hKdlzgqHO>IO=Ak?pRT!*8Y{*pt_i+^P1ABo}Mrg?H@r;bqq@BMtiI} z(Z2KN-Wny!o<49oH|==2t1OrjTBujD=DU*hQGR+gAhnY1~H1EM2kU)TII{0QlTkhr*b*64Abx| zXbQ)Qblr%8Z=EQmW0?}!p11}ErL$K=jpxe=4!y;~?r#BdkW`Q`&8C>=IltI(JOq!(1q*}5 z^6f%IM2zcpiaCmJ;@8bEpt?bVmR~Yg%Yv6Fn^^2I|EKejD7o?oONylRMpLc3w0(Ir zcatTvQrS(rOU@kNaH5EI+lSPItYZdMeJdTkB=?(|4oSn6mo6>9Sf`d(|Z+UxR-74bdKb!M`3INYd)1^?#Z7DEP)wg<|tz-q^ zkJX!;YYzO^bKX5}I{V84&Jj$hukBRGP$1`hmd}RRXlZKV{NzK@#Z`$MOE0$!Xo_(X zY}1T`F_73-)Eh5sy(@G?%^N~;9|vkT9D&XDL-6-|+m-WzTq>A;k&v*&=0oM13eTAU zdYHXI@CH3b5_<`9LtgHuih&Pm5n*yoZ5-HgAS@MYvgyKlnlw5nrVRKRcGW_J7#%A7_fKbfGF=Z{Cf~!#5 zM&8mjOCWHT6DzxEQc*DVTJS3$ES6f=uT_>O*aeAOa8b>Hyj=3L8^CS|j$i1vSJ78~ zZL{AW+by{uXC{>Guw4ZXP}gm5D{95DAv5d#h&#DS>AC3yJcQ(^$q`O+d`wyG{|ei;?#qQP-A#fJEU;TI_%Uk74P}0ww77@V7XA)Lb&?3$%h_L@9G1 z^+z+C^N%=*9uTCvK5U`TIYQ5o=W2HwV^9SME!Bo5x4@u_g2FWMo!GEB6KsYv+w0o= zPJj;m9VQhZW4QfJtLRq&YQ?X%G+N(WZeS@@{av+M%3zZB+G&f?bB&F4J$FD(8r-Vw zlrWbX@N#dX->6ozr)(* zf;*8fjl@sVz9}&CK!SpzEO?z79k7`y7=J$oSIvW1+XZ3VLzA9q9b^XPE7SGEl3jX8 zR?UlA0QlJa*2=XHW@@}(p+~%kAe?GXMu(iXHPUt^G~&R~{Z(jHzQDC-?z(^ybVv%g zLcU~TeX7AyfWkT=hdeyocEseMzbN1``(YcW@A#YFK#lA_y3H%f|EB6!c?5c#`%rGr zYvGF7Vln0cwtB6Z4A!`y$YBmjg;Zn-nOLGJR$8s(f5h8(ZWG6|2t|^`s}uqij))sS zzV+&aX(40s@04hAx*-=+R^ls{L>tSrA9b|fcx<9C;xvpEz&*9DQU^U;5UyR9zo)OV z;ea)~EqGE~DJCUz`L`D-s1WhZ+>EkB_qNL{3t7IB3+T#-U=rhKJ;v+Z-y={mjm+_- zdk$+}_GbmA5TK8zSQ7zhssXD=3fZ(L)ZWbvsj}*AIS&MH{-Cw)M`=)|lKqx~i}AS^ zLjYH0y-L4tDLVpC-L^`rX{L*0<+GoXtS^skZQIqOwog+g@7oNSG0qb;a)9H}}H)kURQUhtRiq$LnZLPh4?6B5sl2-ZC9X-b=TacCQTYX@i2n=0+wlJM~u z@8koW>MgAWZxdpvl0W(b)}a{TQR2J9aDnEHCWpSY;!#vh-!Zaobibvhf za(eH4dLbtqd9AG=p3=03^uL#ONJWuDi6qr9Z+7p&FpyM& zv!$*&;Yqhy!y*x(g)g1M=21=3r3>*X1Io|Jt&Ldp2;L~JcQCO9_Eu9X)F94-H(Ym< zIUfTN(W8VnsemQchL}8dl9$as2Nrf&!%H{Mx2B)k^9oaR0^4e-J&E1DDn2V`@g=jBzrjIw>!qKv1M95Na363(s;^v4rjcx{X%SDA0 z^=gj=Udm*F_+~#q3E&$x=Pj}Mz@))RMl^--#D$iz*2McPvw1PaZX#xXmU@HIgn)Kh zt$8L<%F77qt8xNFnH8EwqpYY53wqDGQiCP`$Zp7m6SyJWEYX*H%BtHSt>(2eQE@X( zPSv#ssC}CSEy^^gi-Xj<&SMAjO&Zgy7SW_zQ}2naZ5`G``yqOf!iT*u?mH0tBb913`T)6FU_bcupAo;X%Bs1a_e&vNg zRJ!wEqm8znU4GTk=C`%hygy3a7*wz!AyJNcGQ6Qwy&8;7PHL}-rvFwhkWh|J4w$=W;kYK{9;rh`dn1FE*x@5=)k zZ;`}3@#eisl0WvU50N#FoY`Ybt+flE1Ouqushk%{BI@=rohVeBh5eMqfFuxXN#IWq z!gI%(wnXv8{a+SE8yq_F#LCo6&BsYfQ(G;EgLq&!ShA${&P>)D<9h(zS>O0>!DkU$ z%ut7d%$8e9nh^mzPR*q7NBjxZ-65OL(pg7dmQWVnNcaDpF*$8PWz!G?$Q6}3*EO^5fc2213#lYNhz?~}q<9<>RD%44Mo z$>-`Q1M(~GXGxd8szg7i?*axCO;RpfW>924D2q(+A51p|X@3+kfyj4gEGAYLgJ~^r zW&?RGvzOd*Ny6X+9F|&au+%#?9EgKpm3l~_<*OjbwA87Pb8g}5KsNyX&TTHvZ?Sq8 zXNS&`X6>k$Gd)o??CNwGkw`L<<$7IfPSqnzJ}n6(PJ_^0>FQ=*%|OW&*-|uUdh%4L z$C+<|M8!$C>WNTi5jiXU=)m$H{YSJr0Jk0KE?QENf0fs2>BKZiPIEI;PIR}X^V|#h z&ccPt#~CfrN?(B&2jB||*bWSZ*Wblwk z+sXRKn0bFhhn*W6d!+mUOe992MVZzz!y7YNl+xn01()ELt4{Nl=m1GRcQhtRBhhc8 z3Qw|=%e>LhMNe2cA-<~*XR^jX#Wci~z)c)NzC?jPD3>f|Xs|XHW2hlkVR~EeKBz4# zTP-;@`v^(LERGo@t?^)qdeosN6hKp?m5u4}G|QYe@R_mGuL0?$PBm$NkO@i z1R2JhCl)(VUiO8J2$u&BQn!DzNf`pKE>dhsvJe*Bf!}(u15y-Rf9M?+U1JThvqj9O zG|g(X&D&^R!FV?ip#}TGEQEL46^xiO!n{m7zg1ZVhOl7G%ji=BAC5fPVAjOOkCkt6 z8lEav3XBq0Fvhnh4{c9(dg{ls!vIlH{D>XO) zy7J^o*VSGN1K)Eo*jGGp9+-iR+U`K@46d)BT-00Rtq6(>`(w}kw4vFyl-d|HX~njV zkvUE&pjwyUmdHDCWam4jb9D}RER*q?Nv1PFe(UprrnB7`$|xJs&VvBkLqjosk8>0R z4Oa$kA6qrs6x0>#eVh%IcD0b87~)|=r=LZ%|p{jGU(hbe46!PoG51o? z>q0`7f{94fL(nJ-0c4A55r8yg#D#q`#t5N{~`mdGQVn zcnOvMQ?Ea|tIym%^J$c*F(;|C>gSaFp-hx*_mZ@E1Gwhqrb;bNg%HUI<^HBXZL}eH zO&*MjSo+XSn+rb_5G>&PRS{{np~gnKM^_A^>N&296`tdrH8?U+Y+)BhsFX*v`p=ad)^%VH@5<6 zPcItI^+@OX8*K#%UjgrwAK99@h2);wlfiMODL!zeiK@@&RpKT)**nGHsugd_eZ?=1 zDAh+UPg#KE$t4dEo;pbh4)aD@BGJj_YxxrP9h_!_=l<5at>(winJn9*`5UgCv#%mp zJ{`O{8}^=UsQJTTjajeQ9;|U6lEf8E=(scP#Zi!{;A<{`6eQ0`CddtWov&t)K0wr# zH#XAbG=g%jjbesZDHz~KMQA};Y>^4v~6r zkRn_kImkN#fu~QrM;6!Uu_v9|{RAI1^P!pRS-ZG{Y!D#7S>H|~=69Uq?%$tN^YV`F z5nVz1czYxhk{Y@~Q+nr_1$>ZFN0kq>{ohO8@cO!W4H~@gZ>taaxDZk`J!kGtGkF>qpjcQ5?VWa)qy~>4&c-&K804wF{{bn*2%= zrozLgohBn1x)oxri-?{oz@xb7X}{F>b}6FJG4>4UbwB=mwQhapgR`J)%hAc%K4_5j zGD?UGD#JCE;amyaY1meH(VMZ1-hSl;c4`Z|GY=GdQWTd(cVjvMF2fPopVvc z>5UG=Sm)pyJ7gv)VB!APkM`izB7$Svc`pPc1B=Q|@|u`?@sfpni9k*NPBKIZzyKefyXV43)0>;<6P)9uuAfgACB- z?vbJ-^4ubrVme5n!B=}E+n}3NisN`YUfI6C^+WM1@{XK}XtB;%8CIRq^Ym(zc;W+5 zL0d$F<9HStu8(w~>1dp9v*EIIvK&IHAxRP_@LUojeB;q1raqPLE-9;Zot12it4B}r zWVr?R=!l$u$60OGlW_zXj+5(Tpc+P>t<(uk{M7_%akE*fvZ$t#p>>i$70(iZevygN zD&u?jzFe)QoZ3Dz1M}4kS{p6}^X^c~HsLXqj!KyhBfiYiW`idY@Rig=#mxnG!s#bg z7@)-u@W9_wx61@w1aGyrbJw%295PDSS^a)yAJ`TPQX2NoLI4a#F1U~@VGUaO{%sTc zz7OHKQ!0_x=Rv%;rTaK6tBS0f^SM9nLdoZhH41?ONrxz*0|wf&df#rQ_-iqX3~6OE zEZ|53M`9L5+>zmd(Gf%Hc}s>bT)txL1*wDAUW$+pS|2X?YiJG`vMe6N4%V7Z5@zr+ zO3{9iR5t}&B<*9hK^TM>60Pw-u2Acb2!uTLc;1^j{oSn2Jz>O?nWIQ7jkWir@~(!T z96(U$1KkL)lO!!lMB)?jOf*{p?lO8G?y1Z}i&DvWK~;@h*`kJBb>>k1ECzt&6DvYY zL`IQt|2Kdze4tPR3U3gIyTk0vvJ~Wb%Jbr~=>un1DiVj8jynA~OR3Ct#nP2%WS=04 z8T3{Ljt3xMm1X^7KOQtD9-iGq>x-3h&6A>udZtJH3Bl3NzC@4c(2Y6o#?F#+jw;rr z<52*RFuT1$nD+d_l1=OUC4B0ERMj zx-EppwCOI{P&e^uGyRfzY6xhaeCV0kDFQ6$@snzo$>A32!{s<_LFy#Qlk6(D7-C|q z-xu1VyrXE4NjxTLQpHl-?+pXq)D=FSM?>Pueoc>(k(`+Zx09p>C+P2>cdslI_Fqrd z(nY=A%~Z-Cfg7-g2P^n{~GBQRVATQ`7_mjRJ^eY6&vS_h4Dwjaj`9*B^y+Qwf+ znKIZT_sQSF>ZNippV}iFc_Ok1t8Y{e6C5kcH7JTMN9II~PJ$ZWg-f%OlaUBPfT1Za+KEy(i}g36u!DJA30d(BKIsxIDUdMtbl4*45a=g=hc zD}S#{ZLOQG(Ag!YD^f>dwML0OaYy%o?oO|KGkSSmH_?rj=)fjxbV*xtg?jjjvp$O= zx!|o*CK=(;s(lTmLkBMa7M{Vsi!op65zk4wmPjxfUf)8=(2$KyRMgMw0vJ9zb)*|U znmxygT-?zIO24V3?@EU*ty*G{XaIwk?r z4Ec6pa!?%AcPpuRVr)xV;ABn4xv2gsSq$X5^HlU?W(DV&JXlVDQNDyH%K7v7xub7Z5|XG@?mMpq5c`W0h=$wPZx3Dmp7vLAfL33j;th zl`H@ChBwSTEnIBMC~C83t1pUIS*Bsp$u9T?WJ^Y=zC1A4nHSy8WWH|9@@k8zJxDF! z+Ssb4IxT<^@w1Ps4^7u^MH~2)$WRQQ59yCV^YOlKhEuyR1}C9r9+pm*Bzo*mY`xGw ze=%;8CuGp!|Lil_NI~SGxg2#wXh(9w?L>y#wLdjWQ~x>h*2mS4MD{ep|LV!)IZrAFP)Onr$ah6zWyQoA=AvXec;IE4}<-NcBfA)MNaq zIUG|uvX^t@H0EMkn}fsfrhpO1v__^+v&(B82yEV*w+1)(*S4b_GvKxUa>-v!ZPXF1S53R1{L-YLHX7wm5ldY`tj}3kK!ytdZANrmBGiwqH z!@eI;eEw5=>U5Kz#>2(`eQC9dWoY%&Pe4;--Zi%5t(r4le*Td%7?sI>Y>pIy@!eMj zU}x7J&wK!xssG$;GO@AJ+~Z`5oiqM9rlq4=bBuXxf1k+7hm&|Kfb@F(GpS|Kpq%20 zfJ*Ec;)Odin93zMyntP)_>g=m4A=Po5~s4lhi|DzxC4?kYcXH6RT5dQss*xfxg-pH!S>HrTvHn+YIcw(RRGdh}u75$Yg z@DG=9dA)yc&W9;$hmUlb|47Pof3#chQ~Dzrcg<=Dssfg0{#|=K_>B(~hy4};nNyw7 z8By=eaNV+$$pT(cor>I60&lF!9OP1xZJb+`6%$@1k|ivaVZx7k8u z@D&DMU-06EPl23GodGBcHZQj>w}o6qQVNTdLqy9RCmcy|TG{Q1>Ec=n)ezqjU~0PX zafxZW4i=o28>07V;e#o?`yX%B*fXTu*YJ}~LDn7^2EtWT@Bz#0kj~;$7mrUCXw$$2 z5P+&h-6fU%>_lx3^i&jTiAhm7?73uQ$z?CU_JozYF?@4?@&f`Pru&FQ81kklI1ksL zZnu{+Y_@mA_#~p{4@Z>0H?#FJ1{JSXI&^Z*NPYk^m2sX?!d&FkSs^%|74WI~-ISN| zN)+YMLhv^`l5eeZ_*@q~HR`RY2uy%Df+zP>li9y@hJn!TW*pD45O4U^;%-!h5@XLu z(B+Ne9!=yYJk8n6w9uXJeRr>AgAaO_h|G#zY#3BQMutoEDtYkX$(WSB^<~y5@NbM5 zJoa$r6x${BuC(v!VGi@a)$DMQkO1Mgn@`uB(h>=7=RhVgVZ1BzLaynR#E|hdQm;?K zy+z8m9g3tN))?ieSla}^8mP82mwJFlc+?qfM7Olroj7f(c%O%ddS1`t@`Aig3Z9ZE zkD!rIQU>aVx*K>dnYY`i{X(&AMf^=CN!2-Ny#DQSGP&LnZ+vg$^9`d>6e@_Zn$YPh~B_(I_jm1Zj`YLwjnT1r)Zk zfB+x0H%uTG-W8uBbLXIKf$xosoH1hK;{3PEb zUqs=UtF|>LO_u6Yo=JJI=-pnV$*Rd*@@kLTAYo$6FC`hVj81=t@YnU@{~$+H;(nFU zvuBaEa;_3xFQ&V`Ghg+Qm3oXMt+!|}!Fj}0=n8(yQ+tdJhrHRFgkvfI{26znhgrIU zeU1GpWCTWz;UaDpACbsJ-AJoyHB|SG2(2(GV1s?cV_k@b-qGrGWi@8h2K=D#C>O=w z4nA4Mu4CC5KP3=}e|l;&#Q%tY7~BeyF-*eyGHMe6WbhuW!utxXo!@k`bgy@g+vGEy zLx{0 zo6^-VeE@Mv3mooM_wfrA8z%d_t(f+Pbhp3!#x+6m0mw6HV7-8`)US^#GKbsAr z0B}B1Qi;;<^ILzRTCcbncsIO}qwd!Md+M8_UmuYAeC`JrYx!oj&s{)(DD(j>l^0Dy zep80jjEOvOU92jyH{N`%JVMm2;ocyh3rwOA>$csidU(i!I6X(eRcUs&f_RM<31wGr z970`Ir-dJxi7lPRN-)H^f$z}4TN0>ByEcwuBFDCgrHaWjBmndLon`3@+wZR;EiqM< z)l8$hG|uMf`(43RPlni1r#bDpQ(5?NiTPI8iKGHcav*Y6<eR!Tq!gny>VW*9hY?n#04myBw$Xe59Bi%|4NCdAt=WJDth0 zzQew2L{P*W5w>;BR6%H`?&DQRHnn7^Kf+B+F>3=A-1)#~%1>UA3c*Jp8v~#`t3-hw zz^$)1iZ)ZGg5Z7H!&a9I&N^nGuN%|Xn(_>)@(yIVvLkcHc4wxKXXRH^98Rc_M+wqp z6ZNJQ7nJNpav(9LZ855M0|HE^=wJCXw0?YZ7CPBomIGgx`xbUALd%dp8^3hK8`uHtu=evaX!;sEM&!BFkD}`f($u++}Q~` zEPy#Dhz43kK2|50Wg4K^*#Lf*!f;x_XRq> zEQcyT2oFw|9X?|zD|i~MOE8LBfw;9QE_pI?Vr9HxAOY&IO{TB2HQ6rqzeVh3IZcTx z^D)mOSB?g;$lWZB%r*R}OlM;6t~9kCy3F+lWg z%p^P=j>)0klo#cXjB&h)DNc%0(620F`)+11&tbzqtCOD75=Y5Ou<{ZHu+wd#nydxx zYxtI@N-o%q40j867Y};{mDNYcGC|rq>J&t`<~cZXSX&UqcuZ*qmj}(RfCjMeqtp^i z1{sr1n*n~fX|~Zzy>CesVSQX)rA??f!1%!757M9O7R@KYVNk}f?B!*p)ZK^El{6CC zbZY0T>1nAFNS2<1nDBJB*P6lQz^ZlDA~;L{!xAKi9#~Mk6K|x*-%;UB+{iEO>)8)| zOiix8cShCbXE`$wO3F&RCf6gOv#a)MmX(M!=KdJk98b{X++XRl#cy>n_T%SEyW>*Z zQ7|R*22ao?*%-9t>nrNqJ31kePYeFA9>%lOQnRZ>{m<_+H6EIiNZh8`!hpOs#~06h zWgfd%nmU>{{MdVcZdLdZEP0>Tf0W;4DVx%Xe+(i+9mW@G*J{_2^5I7>69iQ%IMgN{-84C&UGk3qJoc!cw)1tSKggMjBn zJjd#3?E!yCenp`=RqriSFpiXu^ZPbgi8kopD(E{_c%;-^MClP7fAtxEh^H0iBePA$f2@+b~7iYFEqA=G~ej)3>4J}X{s+>OA`VTG7K$s(#S0Ytg0dea8BDD~r3 zBW+Fg3g8#iJ6d_KCb06Z?Imf%6}oZKj(o2T&&Np~?pw$aqgg-NcQ2_ciXBQ|B2C7& zydt{9dRcL~P9@T0){fh*lNRu_e_~fLn2UL6hwy+@$FMa)T&P zA~qI%#Wb3yfrP?9mWQMmU?Isu3Q{r~8_``Vw5$LxlG}vDJY0W7N4j+4X0wrGtfoPJ zoFxhNC1hvltdRGV6_=hWgtJ^fo*%4x9|aO_E|YBf&)mBj)JMP>XW93)7zI!m8y$D8 zj9U<}=(9(thCI6gshdc%Jin8SY4UnH*a0Fc@JC~2vYSA4AgAjy8Yj(Eb;8bGOY#`y z<@N{3-eISkMAxKz`;a7tEWE(I_<78{SKqarDWm(o@xE}YFJ?dsZXfM`e{^V=d>Vbb z325}%k2((m!qZG3~56zi7%C@7Fw=01RT6j~AGy#$)vuKkO?U9PR zrEo#J8X$*AVp$}uEZ#n*##=Y*yTplN4~~cSJO;Qj1cLl=?8QIZ9ULrLbit&T84)SW z4LWvSsAlC^iIvZid@$K?-z7tLIwaRRsWTxi>2n`(;MoBr<^1$D)Jp@sQ^QTcLK$pg zW8qbCrZ)z@!-qMvSwkRl_-mv4_OHwCnZe0SA&adY@tbH-7eC&}D<}#Zs!P|%MBIgT zkLQME5@9RnbChg;gthZ-pg)V*CINV%TUZr4-vvLdQ(G5C6IP z%MY|;@`_pc=Q=QT5+sl_a^LtT>S>Nonz<|734d#;_#uJ8Ks{cUU(tBkhCVaZA6s^o z?|?2-KSwlYcntE{wLfj-{Ho_2VS~=ogXyZIS3fwRW7~7eYD~V5&h{_UiJ+YsO-+sH z;B=-^5#%gKe{N+XN4JH1wH=5#T8c^|1;S!&p+g?Y%1m4=5-W5ntKJde+k9y|metq( zxPS0QB8;tVw{n-hkgDxk0fhR*b;aQ^Q;rC>n^Ss@+EY=hVWd52%^J0)iw$jUu|5?o z_)1*qG6pfBODiWy01JMGH800j8>();l302!Jd?B5T2ulJSrL-X1TT&yz=Orhs*sxn zSvk@xo#Z=lDWs!$MdYMEkh_aI$^GT&8-DmP%hWR$W3f5r*|;B@(~#Ep!Z=bTCXOP> zQ84U=S0=_vK+J|jIcXUjqzk5b6yko5KB-Ss@`=OYa=2;sH2dG^*?*)>dMTb3zKD>D zJWh3cQg?U0WWL;Zrz2iUU;)K`P8`l=qd*nlSRDKzRsJu|18$@@2ZfY)XvyAGb&)OX zLn@LKRYCRoppA89r)lz2P)oB6(o!H&a|)t!y`yw(l~_P#7sQqp4)-IJE>3y(0nd7I zN4y;71c%$$ZjqprCzB1m=6DKk^dimhAU~CMt`E_>IC_tSU#`}>9I5TMXefqIH*!$L zzl9XAG+~m~OwRO_zG(@6@6dH3Ru8 z5GC)Y4;X@#z#~T-J-f}CNj)3-FJ642$YkRc3a&o!b8n8XR$)lLB=b8lb~jtq9L&4N zK_)|{6O&#T;M-M+%O~YwQkhl6r@+VYs}hDyY;IO8L$BMIHV-sht@XknQ73UJ06;0; z40b|ZIU6(3dhn%SpqiE~?ynD8F`{0JcoaCFqh`d_h;{Nye;{tH1cN!4Do=WiW6%ob zncwrQ?*}9bi6p&)IdQiN3A{L_c`g>1FbU|c*ft?;Q_2~o7@ceubial#M>;EVwm74I z-HZd!^AzN3s(0jJa>y`-x*sCiHGSPITC*%P2?{z4#2%BCn)pEMI*^h^^fu z7$jnvFpKv)U*QmO%FzGGI1#K0*G+)q5TAQoAj-O zBt7>DYu>^_yHM+NvEdNaavp4LDE-a4G~%M2@@}g$OYhc?Xjw$QkJyCb#vu{9u^=Js z?!bwU9H2WTa>}*hmt)r-U71069~U$?qkwsa%v-^;MVBmb@M__CsjL9XiW`=g>tEqb zJqdf3ldOhhe*T?KFcRX_D9HlOh?&`{Gi26d zC|OY|wjBQ&j3mTwHP?|Mmpo^Q`B>~=t+-44wL|!lcbz{|hcU&p#k7VO`faII*XSs* zsGhJ-Lc+O$t4ucqBO$J(9GRP%N@3#>a30m=Zq>$LM$t8Oz^`9OU|#?yWmCCGrPb;V za#v>R$ZYbceXT7qU%d&x#~gj}5__ZSqq-c#)V5CBkyT~_5n#M#)uk(Ow`cZ|7pO8X z^c}_Vil*L&RNei$Zf5c*++EV+oJlp@v4~c4S*}#4{yn!GSZfUka_g_{m^7oF{e}WU zO(ue5Bd^s&DPc0`gw`f{2gV3ZsF~x+^J_DRLiMk{6_$x5!k*`Vt<<-))<`aN$Ig#G zYkrpFit3T5OU=uZ7^8mjVz1TIj4vlbj~?KdY4-bLyOj}j$(+VdQMK5C_R=1US~TyL z%w=Ee)vx*YJv;CI(KIJ+r1+gSqq0fKJ52zV--aTQrsL|7Fbi(PC;^mB*{*y}0O5)R z|BhJsO9X>#QG&n6Rt)WPK9`4}lIm|;L%eSh)@S9M@+zs{29jaPZ$e^zu+)SsiWE*q zs-zhn7eUhe*49gpV1_(|YEk)q&9++GmD4%#tk<}cVd`Q1FA}|$S{mpZBL29sE2Z=a zXj8xUa^{%f&3$w7D?S}s(h@@Wsdx&?_^%g&pDW8ce&Zx2Yf<5_{_hE36H$KM9ah@PYODKDkJrDp_*9?>mq3gELLukKXvI<(`U z9@+@bx;3vq58h@L8Os8X6%xq0<#M62q;`=oGy&ePCbXIwL|&swD(zLgse1T>_?)B` zobx8Ss1}YPKrplh*v(p%f%yfrr7x+bG1!-OJ^G|_4#yt=(o`*(R~;5zV5Gt)MvJ&vQE|H0xP-1NZ5hm?u9< zFh5!5yZPyw=TQUQI4k&H+U_N~%m7{~-G&&|$ z$b~r7=SU7LY0*ka&v9^UwiIv#{Hjb}8}6yZdV-DuXR)~NLM*FvkcM;|>2LnV7^2p3 zkD9VBM^pfJxD@&1J+kOMz(-ThPu74b-LQO>N;m0&B|Y=-oYR&tOy$e-7xVOp7-$kd z_6~fDtv0ug$PpX)jW&aP`})YNfujt`TH5i{96X;V-A-2772-4M)M5wGHS0#MRf^2e zsu#WUHssC;#COE&iGh8o7|(5qGxnwi%0wI$*8yJnzH@2`HNDVdKK2IXF`Dk867PvC zh0zDs)o-Np-l;JK7a&uJf&gM3;1wLT9grSrJW%0@?qC}p^Ilnjhb!uy@qT8_*(L9I zYA10DFx{<{>X*Bd$s2Ic*G7?mkw7BHT_W{2vF483q;XqWO3sZsfV!&+cm3EHG=UCl zTfnYQz?;BBXP5rkD~4CNxJYvXLN8~HmrWcDF@@?5TA@Bliz?cRpV~OPvBsIp zCP4Gc9x!0Zfm(i}A~~)QmI;E%EW7dz*s^wfidyvzU@gcLmG?R;&75MA#s|B~5C{|r zFc<#OKil)UA1X9&8Pj%-#I(5`6m;sWSjP7Udh5+cl;@zQ z#LzdLnV|ITkWT5e0U1G`$QQ1ouni8({O6;L)Dyd)osK1cavj*Wx_YYt{qc8oVfy&9BjP1>_3sg7xg9f9Pz6Jf1T z85u{y)&`dJAxo)E;9d>tMrAb0&8~nNM9?;*inD`e-wuT%+X^`;zCC*79B=D-LEFwx zp|`sFeQCr{Hbq}+lgE4^$IB^|H?;r*0h$6DwbQ;tPY;zqAOk~SQix;N3j}D4rZH74 zmbbx6Pt!``?s$XLgLC|)EO48f6O87*oN5dz- zrlI}gkNS6C$`5`@`L57MZ)XjxBM;d!j?^7#)sBlz3=izV8hfXS{!DO`BLlYOH<`S5 zZ@)aEoSyp+mop2->L?g=sd9IEgVC&kaJza*_2M*cp~X83im*vDR%lXHfI%|vuyR_# z|9b8h(ALUF;(Weg7PUq5y=Cht07&wdS{nCjLqS=S$=C{RCqb>kgap*TgBZ<+CxDiU z_N4Z@PgSFM1-6v?EfJ3SvG*2Sqlw4Y_R?}w1Y;&bRNvJR446Lp)IU4Ni$&am9U|R} zwfSqzk+H!9Uqvy^Rd0O9UOnxtUcC05D>%L|kvmVT_a9fEKfM2pe-&n~wj=g@+c@gt zx-r_~THaQ3IH^d9lljx{Gj6}Ir~em0v|e7s!%C}06eL3d{?|^{{B2fAGt<)oPT(F| zupI^JJ^$ocT2LQihOPw=W6AmgCH=XjmrM4g)^J=QZM=}4$SO&@(5E3OysYJLsxw*2 z@)aZQy))fHWAlWXG5}r5=K|ZXL@50KMgV=YoGda9r9h3r1f<&j3zg`qdF7aRy|&c0 zch|(Q_VpL`T9x5bw-YaEBs#dbmv|C0=`va_q2@18KVc-}o}<<3Fbuf>UtZrAtV6Yu}cki_$WP&X?`94H?coR$`2222CE=u zbRiJf=@vkR;G(nfrJv@BiaI;&zLqE`)L?;EGcmgCya}~ndip%e**JQf2&;XAlD8Tt zv`{@PjeV)1O2`zsCG3mQ18vSTmCT$Gh9r+12lY6(rgi(Im%@pg7ty6WgAL#lO*Fp0-=lVt-8@z2Z&RCO>%31GQl zg*eZDUy~-Vi3gs5GKVgBpI6*FCf@K%cbv{s2e0g_$jc(z|@@v(X|xG%;upKsm=MJA)?AEYc;NfXKyO5*k{>>88QT-OVai-7UvunF#Wq<#+18>v^i0_R z8R4)NArPl``z6`a$8;uW42mF(ouT*`N&Sked1pRaEA{nR@smtb=i_Dj!lCid0S=E< zJ|Za_QKt*gkQ!EcIWZ^`=6Gcp4aOFxDYzN60FrZ=^1 zY_Re-N5*KOAN`y2697S=$r3(j9%-D<_tHhxB3hTCNISBrxZ&lGkmqrR{0V@ryDTA& zxJydv{L=vVN#l-FRN63L3FF0ZBC}!d*jjo;2pb~vS^g&!1Hc&_`~SUUz7)40%k`e^ zZerBj3NC;FpZj8mq@aLfFsyJKS?-Nz(s4}SHT=cYch8rvGi+&Pz}1gUhgkS!I=ow< z^+gYmG3|?aIb}wXKi{QhkAFbqrv!Cd{@ff};`Y|#=E>gut}Y&U?hB8D8`?LXgRrX9 zhyuq(VxW%Edd%B}4|`7@jH-Y#;6CuhJt@Qxp-WL#C;h1MDjUsV1L;PiIzg}WIH}Ai ztj{O*%f4^4FpBbnCWSA<{eR~UHh~HyrG|=FB|7+C!cc+tC51c|;UxVIC&NCrh;)); zD(S)M-aP-lZ+zHCCNb#sEL^quM zVkexi4A^O6iWSCDVa45Gi>iU)h9@O+dD|Nc(Ego^vk{b3m^e*tulQeCa zn`RRT`TqzLBNNXyeMP;Z=Qv12zgp`Qy(Hoxc8IaYkW_@HD0+&_%5b8^c0y@T_yb9_ zC?Nb&RM#7H#jwj8k06@sGOeb33j{~61QPYQuPT`C41Dg96VcDNVJ!h5<$Akb_5>yT zV&;{7yBrabaak^v9$6N zxTO(&(3~yNU6h2rISG_#JjTG;E2_Tn|37#Cw&l2yq=~}!{SWnnNR*%B#_deNSfHRT+&8VXprmB}T|=!);bi}X3=^9oNQKGFr8 zgVJft7lg*6(uH&pR;|FM{60}6ou(qGU$mH-|0cmqaXf%k{Jm2+P*6yS(cc4@fVf`k zOL8$B-peeH0T3zGqsk=;B$yDNZe|>YW>0$^_W^MB&Khe+?-n9`R}Vy*v!v~5D;&(s zXqq-NC1s4>x&qFG<$DdJtWC2vix4b*rSmFO2dL$YHb%TN4RHGSXlO#&mTUh0UvdX2PBm)8v>M*`d-(oEqMgln4;_AjK z=eu0G*NGn+3-+4+rY>^Vi50fPO`-m!r0j+oHS8PlSy|;%?NHSRA48&7rP&q19=ija8`~oscd`Z^T-}G?^j5W+ z6d>n?o18YOe|g`Y+8)Z)73-h;;_4s;P}#jU_OpV*-RK~@|16gvBWSK?BCRMz>~oI%>s5>FQchj_o_w$PzdI^>P)Uu-@Gpv_=&SPMmo5#pw)RVRmsy#A*l zT^(dG6V@=+`Hn*aE5e{V7NK^v4uf%>N|2W=AXGaeP*zdfTkzRv3ao6;zas>f%{=Za zhXPv2S&pw00E4TOMQpgi*ToQF3m^1ioeM_qnxMIx?nYW~PR0wd3KT>0Ii%hzbwN z@vR{4VfGx8Apy_l{QPzJ*Vn^*~hE6Q-P&g-kkYU zi*n-clwQZ8Nl&LyZO?|KgK+Q?OiU|X~mt1gk&SF@Rf~_+m zYc|{CFpkzEP?{1Yxl!5QJUAg%)Sa<26m>*!p!Rfg(LlbW&MI|~WKM50H|gJ3$3^5a zP6Hif6g=2WgL6)Z_KQq-=L)AxmTw6fiyY;j)hO2@{DEDU+*A-!TvlXL#N)x-4*dE& z_|I<4A+PhaY60x?%m6qjGCn!hRd?)wkK1PfiQn1+@w02Cdl0QvF}@w1Jsab!EjfSc z!o&Nqm2wRinsPA7S^aV}-gnZY zO7QJX(~zu*0USNk*GW~}3RvFvo3hNV9S>>@$k-m$HNPoAgi1_E7o#mjq# z%jlDGp!03py^ie(%K;LQ0n#bBd||50N0V$d(f_{4=8_`I(R>4}W&lW_Dgx;bj$d$< z0&cCJk_OR_Q{3FSrqP!K9SE5j_1r{_n=FilG+rKIZ1*X;5ZWfbu0kHfwoc^wS88@0 zw3_kcXq{5ufiq$g03}8WA^-3_#st`q@HMQ{b*BMN zWETR?vgDOw<>n@V-9QEC&gD;^e7&G_hL~kLN;(Am3A=}6#WTiZ3znrODLM~VK4vB2 zAWJk82a{Ox6$RvVSYVreC#*Y&gc*4%-lne%t^rZoA@&Jvxb3`lBJstF+#M3Lq2pke zULC6enuMIEZy)THab#9#$mZ6vKE_{ooDqC?b6q0;orLSc+#+c@F=$?3FJ7M5@(lw= ztWkH8o=4pE6IE*Q*xM&JFF!Q>AW`xEh}(LBl9XR`3;s27nHKeE`#f}p@7myo_vMlN zU4q@(y!&mWJ1qxC01f8$^O_i&W189yDP0U#q%98&T?;cj4Ro6Jjq z#r(wR`!=y9{4tp?H8%1n@3grtqj$q~dpz0yH5?m>i~c1(Gq#pVaT6$IJWB|$$tO@a zI=D5!$gB36_J_#d-Msv{-S(|)P_*h-wJFOsR8EEFym|TVG&U?fKL4V74&F;y-GBY^ zOKULt3oD}G?yZTun|c3inGS8+CYp&y=*EFNd6|ywy@P$?p!iF;NQrZvUj7v6JZux3 z({DUPgTcTjm7E*{5&rgIN9ddRhR`eR{ifowDR>~8xVMUz^jE03-T5B%!)>!lP(i*s#T(n~o2a7lM#Ufh+B9?v zmN)IM^bQ(`+x8fl%J*Gpei1_Z9L3D38LI>uR~_kdn%Ys8@D}g=Q3q?LxT1Rm0{@P~ z9J#9KI~)T)PHUzHyS8$N=%Kl%XdqtZmJq%c7Wp6GmWbw1J{ixRwfIBAW|-bGJ-D`C z+<48LlOS!pXvMA&jym=V5JE-3Xmx(-&8tiX*nTk;(CsO&K%AYA1l^;#aK6Btt~c{8 zECiS(->kXirJT8Jul3JfSqAAN?J2YJNQvThb4z<*_={PTzD$cNR3&R9xunf-9tNAx zsVZby%%MTy%FwhsspbG?T;Jznsqj6Vbl$kU@dLUi{Sx1!`2po=6Q_se>&!33Ob!hL_+k z;u;0+b!>S>Nu40LJ?SQ{WHxsT2Um+KBsdT<6S0bQc-PX?;y5X`aMQfAm`6-N@t>A5 z0>G|oEY4d%eJ5q~0`_qvcR<>ia>w*AVbH+(6xro2T&`USSyGpZP@F|XO}9j}Jh zGLg?f^cL8j37W-WN(VLdiC>IAySB;1<}M0zn4WL`F&;POcr<}APF2F9E;en za++0U;pM4iC2}RTTik+>SQq(&C+!w0o9z0IWx_)bbV@UC^%Q-aQS}c!Da^Zb6ng+) zLDuEWmoV&GheYE40B!Z&j52Zoi49dTYKs)LSbFi;MfcM~#_cN`U6D?{UB;+fMi{)? zoTlVAoR}}Wqo3n{?ftI?C42_dTo_+lP^XnwRs|v(rU-BJ0GEh7$|_hqMVWD2vTgln`E^aDw#?6PuIX@G_$na~0B;t*9S z#M!Vf3Qxg-A3uFQf<>l1q_P%xD__N&?#Ky{$@^lcJG|?UHJFiH=2e8rv%*Xa|7y1S zEaBd&xS`Fzar0=-ZHd;#>tsm>X)&9#yYRWB=K;Gt@JVbrM=JFo9V(bFTSqo4p$z*- z%Mg%EIPT4QG-|S^0x6eyHTsqh+PN=7lyl0y{MLsCoH`6}=+;f^b9IVwJ;-`V7=%0D zbfTeX-tz$|_fuj(I;o~PNLVi-`7$cT^cOG0^hYPcC^Li>8b2$mRH0d8Pn<5E1X;Dx z)%9vl#I3Pd`WQps@hk?(k9dY5-sgj2QEM+8`rhJ-5*x^2!WXGKbZM~|j}lJYP4y)t zsPF>5rCTl9?}Fb;|F~(!NMnwdLhsM7e3FQjbXe6dVtfw_Ghi2u-4B zL4t{FfzX@9PMt;qm4K*+z3*)Gb@ zLIh4zU)q#=P6kaMNPLm>sLONI^=0)nw78@MMQ)H-feYRo^f_gIaom55Tfz79n-2Olf*Cnd0YhzeLvwb4sE(|J2GQZc9hqSHRxpj?S^M5!ro@%WS1eu&XLWR#xB0a8DXuBftnTuA zEy%4H27YR=W9CI;?@h;@SxyppgIpyjaM|b#^W`P4ew99bUwl|hhGcGsKmwYOq6+5v zWg3K3!pQSMs?){bzi>G|&Ce!MVbX%0PqZ%8;+kS~U%O{}?YE`K|7@w%R2;OsW!@&S zyPfBeXQ+Otyu4gBgU#=t1b^RwS}AHU7T`%X?no zylJ9|a3K13kTEU{sTU-YW3UW z1|h;T?=mE8XZzx!S@H85In*E?y+YQeNpJ~ll;;iU>?x6W-@aqXXZ+lJ4OHDL`Ln%G zr(I*l9zi$J1PN8KeT8>xG7FfSnds~p*Pgz(Xj^k>dSp%vwt$5;zDZPoR#5K>{M-x$N<6(;c{ zj2ms^9r!k`OEHTPW4M=k(Vo-z1;I@a+m~_T^0Tt(ZK&UP4t9nS6zF+Ffcsw2KZVLE zTTGqP7c(7~XWeENkn-e$U7*q8)MdRm)NQi}RO!#ozMzXuA)b!wxMVZ2v*14L0`_Bs zy7c}=1#xkUnETI?OQ<3Mv-=kcT5L4aC<@g<*d|Ef9H1AEs8y*4&fdvOhSuk~t~o zq82I9qQlHn$zNdwR`M9WanPOlq?+@2fKD!Z3?9zhjzQ}|Rj6mH*s~j%TeK8{E4hVC zA(&#J`s*l_Y9g4k)pGBLt=XRTfL~lvakhBXCc&9Va2B<-&+L=y((lmNmNK(0eIgR;zK!599y!}3D5sB&C=_$(5hHc+x6H2A|`Y;c{95X&A)92zKun*ZS^6O znJ`xtw}Yzwax=-iG_py10-uwOqD|TqNgQ9hrm9J7VUxs|qlsm%BKYv6As3nlke-#C z%dlmTw5VZm_9)Au8fkxpX`6$T{7!c6lcAj7LX@%Ql}KnbTp6eL+%>Nkvs*DzU~a1C z%-7A!$9$%gN1Ut81BZGC9Z=HssLQ+DdLLPhov|uXn8WA-#Qy7j-8&#g^0%;_Z z;q_vJCGD;*fRd102LV5{!)C&_AhfG^1zK!81Lz4sbcM7oTp`{?aq>bqsnYd&6tNp5 z*0i}jva}@XxaIj}dYDNBTA8&sWVrEwyWoR;n1ZG_A4NV*l=g9*KX58GcdGR|zUesH zw^J+mo!G{FVz1H@vqM5fMc~kdka-{`>;H^jC&UhBi`6+`|F3&r@!R#-O}!MBOoWy( zG|O}D$c~M!#ksq z4QT!}0#RaLSCxh2soSJujbxPJRxE}ke^T#qGr5o9hfYx@bzyJ0V)XaqQrms@il#8` zc3FGki|%y|a=JLGpsT`u{s#~!dbM5V4WAgs(vz!fJLNvHc;fil2#P5^GJ_+>KN6?> zV{hU-cYYF5%z`!#UVZBOP8-+{IHx7W{;ANh2X2rS5N+QJase>ywb2QXB`C#VkPd;L z@&7v~C>(4H_|b7%c~tu4f52Q?G{>mh;Q#a&avKzPiJJv5c}L=!l*EhZwD_vvXTzMI zp~3FJ9ikT^(Wo4KH&K^GtjTXclJ!Z~v|S68{GvIWp&Oxq?Xd1JrIAdd7`dIy!b9Ve zk;Ey?D_18Gt+B549G&JvV%(#seKTbb$kwU^^Yrhnu_R zXIZKeZNAl2`)x@St0UFr}#Xs zT|Rx(D+?!h(mq$U`0&x;#g-{R4-EK4zgxA`e9;8j-kvmoN*~>tyS7oow}tq3vXTrB zhIM>2>`|khv3^YphqPMZq)AuVJwykgk@cimU&2-G-&C%+5>#*GVm$XN>v}0Nu}&j_ zxJ6(WtF+m?c&B{I7M4*Cl=V|!sp|X87~`{6BXqDu9Rsa`%(;2IoVHl>L{Qvu2BPF{eO8hRy3;H8^`KG>+1|ksR6Ltv| zOiT2xH*LnaAx0qKj;nnxk{VwoeHIo;`EFtV?NUb87}ETyjIXjgD_02tygZxOO(UaQ zOjsJM#CWzIcjymDb*|Nm3vr^M03*fWuwnh)exmw4`4`rgCx3U%ru8@My_-Epy}t2_ z*MJg<1H&iq)t2bB8RM*M2LoJ6!kQ-S5Wd;8sFlKOx|W>iJ=n?!jTVP(=M6WhYY{k{ z`6G4Y`zz=@!y2{=xcrSxYXnY}Kumf#;sho60G{?p80{F;N6(i9akTn^>LZ;)2Tky7 zJ#BDjwJpjtQ>r*$@Mu#qU zD4PA~=xO_XHf@~;;X?Xfpdm0@l&JqP%%3`SiGRtk@-4S@Xnp^U|s)1cB+X#hHBeNIgExm;|uFW_uke zYU!eWI2gh;^A9kQsqPzE4B~uwE(si4G5`wd#_|L+2d=H!W{{UDk z>$`!PYEKCsDD#GXXQ^hX(Qn_X=cIEcI!o)&U^PSM8sGe?qz9=D5|;ww3Zg)TTc#+> z7%P(;dF4~&N152mtv=8H48VGurL+dQoXfY(S-okrsVMyw+D9L%1jX)JUSG?*AG%86 z#$oG%C05C`OZ*h4@zJ}7&^>0ux@gF?SB2as)!*6(;76~r zfD3}M`Zre`Ou7bc5J%);B(>a>HaZ}>VCJzn$Ht@ujXxi#Nq!9M&*1KUsWM7ZNOTBva@2E@X8 zVQA(&Y{6`QAz$X3JSp&D50;eZDyjG1VLbWN?opE#+Tp}dmCl;V?HgRH-ltfKwZ(XJ z0d=%cgGs?()T(2&cEK$T>25sZ=>L_)-wP{+Tl1y+ME~3P$PE6E9knk*gP2l zBU8>8NOSM#sCMJ+<#FBq{g50tZj_-VP$#hp2{*9%2tP09qB%DHvL9YwAibb7z>C=i)i9vc#H#QKfrR=(y&wD8<$I{F z7T(*2*JytS z#6OO$6Q0fMJ6jW!lTT@h%Z8??Mc%k8c~4q2(+di~3{R~c7y!SB&NsxY9z-`)7ZH9> zdbCNjN=0J)ub*d8ju_!VTUHmd>@QHiuO%yyRH9nxR_C9G;o|*i)tP%@?ClY<5&Jor zVLXw)qhbw<)sqAz66kE(g;P6$#u5kVTm}FfGMww$BGoc?U)n%v)#yUdpI(PX!NCpK z8{yQ^0YvI4ekM+A4(X9|gCsn5*ESfPERgqo+IGggt3)MV#9|B81VGHi9s5*75qx7~ zgc?yu92O$%GNazfdw>9#I0uOTayg#?vtt-W{99g}n*i5a0HmGExrjRg+fl1Fj4H$` z8h+$(gBVFGvC~W6q4^1gPY@yAm}hz0V&`b`>C`^{eZzfy4zSD-I+~&s9rl1=NL?1# z+#pmQFe$THY$T}Z%2~Vcsx?Hc6rJIq`-a0JVE=r|2!xAMj?8{wLR8Cb>osayhL? zH76eDb=W&}9iCqJIqWX+V*>wJ2ea3Emb3&`Ed2Nnls65eurU1~Dz4ynq?bfbAtLA0$^-Q( zp9Us*BLTrKBE0%$S4v1W555V)_#D~ZBhJtM+)(VV zM6lNN1JVd7c+#&@u9~lZ>gVwIh>@xrO0%)@I<13J+PRJa!)$x5Y zzo$P-fAC}ANIv((L6t0L^q*Yg$k3R)mHBMMNzH;QY7l&WciDynffzEyLO`0wuf24A z8;d+<{cq6S3_SpFNYYM*)$~LGCZWq%<}}z_D>kc1&g8mK7$+(K{SNhn<1IeN7)ty0 z>@-Een@bQKw~gjav1W*y3iWp`?JZRiRr^^Uk)%Rzx7?h|xD%ufqH4(|A=wdyier&V zk|7m+VQ`|%<+`XWMIIQEDO>-~ge&a!$yoFj}n{ z_j0jp{&#ty7B;e7XDo;4j9CCT76j6EtO&)3*t_IBCm?uM25xxCmO<(* zAsHm^XH`foJmP91oQ6t0ao}IUWhSI(9Th)^eZ!SSQZPyxa8$5qp%%vbX7`a@qH!rE{~#`kGR!viuJ~n z1m=eMqLw9O^}$sCD{=ZeSqkxXI17p#{9ND2RaikAcS);BN_lnlXWV^Nn@s%=ln9pZLb&v=} zS;<_cb}H0G%kxCvzZ6p8bZ=>+I);B<+A-{1BHnZ!S^>7N6(LvljIm$>8+-o=-LOc6|8$94iD+gR zAe-8sT}!8Ug;~Kk3=xGmTd-akTB?4=US;NL>STF{p?^@ZAs&K4`~WiUWw=ErsH(bZ3X9ZGmb8a?bz^y&m(}wXx*+xejw}>MgDwgI?JAl6oRMqxI-r9hxkeW>3vb_ zLFGG7`f(*ZbT|8!^gHECTf!*KWhzLM7mKc`=EiRpFqXCI99Eub$P(yg z%?_LV<^87-S1(Au)o@r-WL2FR7D`upXRG%Ctn6^pmM4;{;ad-39?5I~tH% zRdb+kkJz9HI%+1)@4JDq)W_#5vCPC5eVy*Fa=PFQEjB0YOue6xVppP=LHGw#vfOE? z7up{^1pLtIrukV)r+Un$7h0~6K&sS|m!u!EzEkHYjECMiC~Uiagjj!bwJ%W4 zk$wSc(cvo+72$R2Ua^mb1QU3AP-i1k5)(LF)VHp$Rht8S+#D$Qm}yo9yYp2oJh>Bz z@2qw=aO}DT{S@#2+#YU{98jwLR2;@mt>GMcaTHt!@XlzNeob}|?)O3C3X&HhaS!Ov zu~7Kx7100WuFEgtX><^bH^w1U{>vgNzn}PR>FA8?8xw6qR3OPKl!W!2Z`5;kNx4kX zq|`5{j&9*CpE&joLJMbk3!Db@GOO$HyKTEIaeMN%a}Gxyof(=*f<^B2 zlDj_CZH*h0`efaEb+_+=W8Wno6>*pNh5yRNvFGt1UC#q=sNOk$s0Z_$d7-A79m?s` zmN9&L`RAn>XK$)l{~sz2MQ4`JpL9rIEbK9skRTGNPdZ^f!UH(|g%n#VvlUvUfENOX zYI~ssQ>T&AhBP$hGN9lwf}l*8;x`jAe-4VYgu8Q!)8Rr}YhDg~2rn_Z*@~MybDmRL zz@VEsYk0$`(e$IXXBP&diy|}7)R)kVzsK#JiYu3TFfE&<7h=_>`z ztLL+Zr~pHI<)Fp0ysh8KXR%-P?NE#f7qprP?j^u4?Hpd}aC2=|(owTX6Ro zv4z;nZ8!99iu~73-xiE9g~yKaXm7z@F z9dr`>NUA|?-xFwKW93#GPrOc^Pu34k-22b%+Av zfOIPuoE(3DhAjT5-8Xv>#g{R0WQ_s)-nl-QP!rzxHf&>n>L^&BBuazP=_aqJE?Z1u zU08)d{>m?09%YGUOYwor8K@s#O*=3wCzOgK6AeH7;Gg2}=cD9Fo#^n7)G`7Lm3EGo zYq|mrE~(O*vpYz74^;#J*7r;a2}zm>S+Q~QryI`$GdQSXAgimH6^W6o2my3P&S8E# zVAp*lw=-qceh+=NHM2hKUqiXuxg~-->T5OQ4dBdy=H(0xEJhM3yKcA;NJ1AHrcypr z_Gz=bK7W-GHQD0u`tAJ(~qd9VF(3P`~Kq;|`Is6ky6U$deGDn*Y z;M*qaKZNjnU?DINb#_1BQ9->C>kL3%96w++L$apMo~)}R2EqdL&|AeX{h@x1+od^N#6y$ene87r1BI+9LxBcei-lK=ZWqP&^8?c`-jYg}E3 zG7j78*c6nl@5&QC!hZ*Q{DyFAD@Gk=RC@eR&J~`T?mcOSA)V*h$3^lB9V>&T215@B zDXefI^CNlz4E=|nb$N;W)l*t>>hq4iq#lApEAbLdUe>{|9tGq6Q8#!3{p4>-M?l^} zN$leJ*7t;etYHAJAFzy-6>zw$JmC4|nTEpFZ4tN#`~q!i_Mlzdy_%L3*GC?5lP*49 z%}=`GkV=C3LD8{@RR!hnWly03uhwecPHV3*KrYBhw1#JNhLx3HreaKg7S3GF&)}d? zUBA$TbtJ{t0XwZO&F(9v0dW*;u!IO1+`~Lo#Z7AXJ?b%WsZXf9 z$p~=g448&2uvzgP=?*`WWmgtH%NI1=$gcOg`mrYdD^Az4SX|R_1f|w#>K`1iQdO05 z@M>U28WzyPWF3r?GdQ?ONqMO%_o<$`kRs>9s=aF-khpvX!qJtQxz13IC1!@HbK$UE zjr$v)--;KdI_8SE4s=4uNvBQaH3HMY-YX_1_G2S9H$bDb-7UQZj-Rn}h`d{!07t+> zg0Li)^5B!3Q9ue-po!Kg8~;~BgW*~0yqO9W28_|^gmB${xg@g+f2BWU6!8FRrWBlV zamY1B02^rbi6>EG4?<7P`HV>G#w_e4z^v3wZ0C%hph<8X35C?XRclCRqSx)2Oj0tK z3`r~9AGKlBHu32^MYby=gi5SG?uNGl<`VHJlHrMQ1X3$d%&Z#YYPvV^2nZisTKVQT zW?7h-;OG}*DNjprQhbQpUGXfXRuriwKSRvKv@S_;52Ffh4H5lo&CJ=jSmsoV92R%M zc)0j44D6rtUBQbNx;DHQKRYjm0`Y#q)S#$~xYUb0Bg4-sw`0l?V}(mCZA z2yg>A4m>8N%bNcbe^YogDSo8sEbb2e$Y*X_F6Rsrz)mJ>GIT00;S%@I#R#W+ zZUf*?!KXtXPMQuR-3{VJvPc95gnr&dQ#|xL#y&#BpINV9c+f4BRC_1&Fc}2_lX`5C zBJCw^FW0dF{|sH%-Ox{$lLUMK6MepHx9XI36-$jUHbAF`lBaGe7@IYaX5=Aq&K#h2 zXkrcha;Ck4eW$k4#_IX}2qYZv?;Ga^R`*+aWpFRkK9How=|6GH@OJYLY-8d$kjsqM zphM&=1$&J5V>+}B13d~5c^&G~P86BhuLMh9@OP|#`$=*M_9zv$9fCT`DYW9$1{S+S ztd!vH#0w$;(w4{3|8^X(e9-q?4@Q8Ng$3o=-(t?LJWfw~YK}&6OU*ffCm$}DRC>my zKJmBxfGQ|~pL{Dx5~7}PVsd^Hkk%2^cmjzhB{{k=lDHAZ-Hph*Q&q>wh%AMG3|xE4a(l#3R{BaxwH8~D7h1zYL1u2B?%ZeZ?6^r8hy@MqggzX zA9=jeQI1SupXTRi?hG6{9oxC1Tw|dF(03yNfNHKwh&vTV*eP%x@KczC{d$71MJ@ zE#aL)=FE1a%y%kw5L6Ah4%gv?fPWlJAxi+eKp+pM*WD)VfgGlELQ!MADr1iJA!CPp zNFUsMr9SwRBPU>PSJ(pnze>z^qP4>7!gq99=Nm>ElYCjM$_%IHR zB94o{L~^7!&dt@N?4c8*W&kKP@xy6P6auL6DLwEKHisJy;)3H^JshB{7CT+W69#NW z70N>Be>0%>YoN(le3Ak_3oUW@l7=f4B>)AEAVS&k$wjMJ9Ga-KwB}WzD{fDFpa19K4j&*$ghmF=Qj0)onUmAtW-WrB5q53gLYk#3 z*wd|N2e;-$Rcub`z2bc1FPsMuMKvem`KX>y^!FVk*j+7@H(#M}3PD6CGJxiwX-R;c zy&KrC2EO)l)13%X+cneBR2e!v7PG3+=^U2wZfH(Pq&q7ZS>VXHsLKLhX#16&ta!@d zQ~0I$=cY9f4v5GW7+uji0w*z{W{A@TYBsT7IR?t_CrRO~5Y_9+1=X**)n1hBY<<@u zox@N34S87nC+1es)yB5Od}d`Q>uOpfa)v8f6!Ioj44u`>C65RQJY|bZnf9e)cc(-Z ziwwY4TZVYn9e-mGjCRWL(nu?~Fy12jP+G_6(wKGmi0;#c(A zwlW$$V3x(PqCF~I=dApN`k5kQ8vKo=58qz{;?M=srLK1M0A5@w37E zh&n62k~A6q#+qhp-GeQ8`3l?=}lI-Z(0R}&*{?$Zn@5A_41wI+RThNkq7s-Qc?wR2Hh_xUEB!dDhUKPk=pu( z<_x8%x~*l5uU=}I^n$UGLHzN3i~Ee2N=>owr3;sB@4`B@;3!)KCNE`6{No82bd1hOPQiJ znE`>%;@9B?(D1R3C45p2RaLsA&5CI-q)Nf-4fnK{bP#GdKJl+yvUwaD%)*~k5>R|O zpIB8#xJnrbCx2l^WW09Lm4Qme0RgV@Egz#1OJMy+f_jYWr2NlH^3o}-$0p{KMOW}r zzG5#V&PR$QBz1-SBH+BSxb*SWJ^WfuPJ&8a=!}54 zCBc14%^jL4FhsU00Q~m}Fpi0vRf+c*if&JV`$^(|c9tw=b}PJk3Ns;zaHb_JZKbWC z)t!x!9rQm1BvrtU;#md-rl%uO?%pbyS0oqn^g&$cKY_#IaB6PchVSQ&*z=Y+)VOVLVBtS^OGTPZ|3D6%s)WaKl?S zx)(-t@`Z=HDP}S@w7BmQ;|bQe6Az>P0wZQe*=7H{EPp(;-p9qa_bLg%!N zjGR4U&7MJD+ZpdM#_}U^yt!Vl+8Y31PJmYFpKV{cUf(y8S&xfn^>3ge6m)9WHX%5K=y&A>O76&6YL*noNA*mW7UTL=s$;)!JuhX*?2!znU9^;{}<@RPKkx9T=*XIFhv zHd0G6)pyfLMizQQXKoqv7g96$A|MME`^V9DQaVDu?^s|cwrL~pLI@A z=qZ|@q=_f=!d)6XJgWXc{oGhvg?T4Fs7h$48dUlWTVpMpX?@?)4M3>F(|`#KPy+|` zFEi?qkA>(TvDBfc9c76O%*kLvmQ!z*b1tNy$e00k>afy!kXxrJ;8YQC|X_JLmg+9<82MUdLTZez zM2N#LKU_vF;OEPn&FwCR0?#2}29BmDV{d>C;))jnbr4-nPj*V5h`T1zJSDJ<|Vk7L&FN z=#dc9MXj)0l`pzZfzWPl{&@5fW4_ zT=J|aNqpqlsq#8B4T`;;;#9#Z+CW8l*)HIwLcm}f+R1Ww-r9*_P91*-kSMUDpcH97 zY8$u~L|1oc#^9b!YhJ)e%1~#zeK{SwdCBeBD?q%*kKmzQagg3fKzGH=YVQS&R0`sZ z#%HvA(*#eJ^TJNXBLfdp*@)8JY(8T?tug!qGL_O^BCo@+cTKy?^~C8r(BhNaU*ARG ze!)yS3FfaIA^&W>Ne}tel=QD-OBnv%_+08!KBd*Um4Thw>*UG3HT)G-vr67TBo0jT zB_omQ7$ISNZM}lb)#F;%5&Xb8a@;-Ulu?E3Rx&m|J;NJVlSvzLv)!jHyBRxb?erI* z`jqO*o-z~^6VH*F$k`mkXjzPnik?H-EvtH>H{+Fu{$}1PD~g=BHu=V{DLa*rsoIrk zQ$o`?^Eprc3LhD_Aw@n=dE&w3_!!N0-Veb*FU4BDLh?N1Vk$@lHR=}J2fyvQLNsS? zWcyLOZ@)NL*td0)qrxk__QD@9HcclJdc>tS?*BIp?4kICBBgoe3Y1&&x`6!9u}QX@ zbc51JEQc!E^w3lazPNtYmzz zE9USc3uW9qYinm(vVbA0Z$LuQN(n9El97R->Ftbt4W2ekP1%w&>Kw)cgi$c|;0WRq zU}aH4DeMpJ@{O`e)j^&DGH~t^U1Mz^d(eMw`>`{^% zY&U~bG3CHH9A*@*c&M6GJF(05>fkdKPt}0NQYMJK+8`0lmBK?cr1N6K6MOKPxK8Cd z5y)@@KO3|E>{{X0qxCKAuAw%ENRA6d=J5ia_9OLwg$#{(xJ)1sH&PF=3-r1a4WGm* z?BkWxYk`+;e_*hs_`CfE8KBx|%Z(9wNwH*j!BfE+rHd$GrcN5Wu*5FhxZpas(>^wz!pIY_1B1IQaA6r3Xh$3q5(Rcwt{@_n?Ke&r!)Xm;u zqWx*8K#&H#VWHn&#)QGzU8d<^9pvl496Kt6bi7ch#qcj$HefOTJk9%#cIt(Scv=#D z5(_hxE~yihQmFwvd?fg zjbxX)nhn6Y&PYQq8dy#p$++IJ{C#ID8r<0 z#*RaZx5cs%$p5wyB(Bz=)o4o>_A3f@#+dd++lMJo;D(dQaSxg#K9YXe18Q3e`#E?o zGg*M8DMn4D2NTVz>#`?BB(UEiDHHQR+1v+M;CBh33+YE9w0~-L!UD2`x<=CvbdL@)i6eQ=ZIE^x6=+RcT6CM&>1mi&!qfZkL- z$9>-i!C|nN{5$k!7Pw@ee%QoIwYzjJFvZB7K^w6i0jT<85;d#ANzX^D17&poyc_t| zje@XxP$e06k332O?Z;hLdv-v5!NQP+NdY_)W&~Pf;Sr!!%H1SO$8`itjSJ7jNL^?> zMKUDOB+H)ii52nOqx#}=*{`){T1;*6udkzwuL6sxroqyf>S?UeC=vSuX=0KwLT0;6Sajo)S+e;n)uf3lPG zz?d2OM*w0q(!mrUx=u8Ik4yvHU5wp=TS2|_N!`TN6BIf9$!3ekhAisVj?K`fe9=Bv*Ren>I1Z#lK#l?lJ4@YoIniixL zo~-i;TwGJO8b!8=dgJRk32P!D!G$D49rq6pXp{Q;444~^IV#uIl$`}niP3RcVB!+d zkYl3XiY+go>fVG~!bmKV%;Y6vJtNq+$AQrS|?HfHW|{~2XMP?^Ds};vvvXH z3hoz0t^n=Jd{X!-9RhF=d7hKw>96pImqU~mjjJ`~`VE@eKQGt?4^1?!aMG($Jruep zd%h;9CSu+$91+*L(|@jfnTuIcDiA?&L*P36OwQnuBJob;>~K|yM&qB?RV9%oQH>Mh zTN|J6Ols#d!Um?c!Xo{|GKSp3X%=GyWTHT~rF}Ut8}pYSc2+NyE+_EMw38%6DZoEz zP-r+B=!?|+Wa%tU;0d3XNeMszOpGppI`r?ru+qR^oExv{v6-4NMt43U38S>4?b&ld zlt`nWOnO;OiQ@;lf_7d$33R-Dhpw~Nz#U-dOqx_y@+4O;=3FD^v-4Adtf8FHY`aqj zaC)Hgpc|0w6z=vhEbg-!_a->%k|HU*k`qE_8~!EJPLoonv`qTu;XRY`)7iDoshF5| zI&GyG9yn+}aHIvL|7aPkv#P+l}bTk=LE#&?w&FM@VWF`xXA}ysXr6oy%6y zXdz|A{$}>dHV%TB#pMH(j%0Bce4(81HX34kf@>n5sV3P$zW@b5K1nh`zV~n*&C5SR z(*aHo9tJjd5Laj0xBtAfvlhHb11^=9YVs(hkbI`t`UO_$g8afvICwV)beI#Sz%gL= zE`saf(b##DzSn^vC;^&UrgynbGdtOnXm+v>aogV;D8NmiAGLq{kpi73KmzLKj_Sf1 z8kj@RJ%vhfm^cI1KTcD~xdM5RPKIc4`s{t@4(Nafu~oL0%1bOYcv~BSpwFgH6Zvba zTx)WFvO6$5RWZB?JECqA*i^{=v2WmNQq%*}*BLI)o;XE5So}!Ik8aGQOCkpl7***t z(!|8+Ch@09Hbz&1zCXEL{hscHLS1?1rb3*aURv?%1xXn78@G0;rzVRy1Y4XQxt17i z3MIi01bOVz02+?+pOEXUMKQh6TDY$mRxRi~LjIPS@j{ziYA};uI3w*hN-E2Kh3gOx zy4hd4cwo2k0Z5#xyoV0yLloy2FF_V(s{0MfUtW(um$fgxhwLH5sg2Bel@nb3S;~n3 zDxnUcDS6Rv8F`^g^Jn{SOr`6YNvBLX`&!*AB7l`_IheJ8N0eC*$WMlL%2iSOt71ce z-dh%zxsPhuCu3Mk;T!7o2ZYr%@DLCQvB( z-zY5P|A72G@+pN~guJie=g_aEza)l~R$bnWa&U2fbq%usM1IAL=zLY|ZM@fe$hyxrh|LJWQP=CBx0zr<5xt8O zf+J;aaDc-A79{-F*tEctEXQ?!AuAFuG}_X-XT<9wPc#P;tz$ok>jS-zz+Pw_oOvbt z4hQIY5G)8QOSsDP2X@9{ArSU+Mfbb-ZI7t&tn5-(VgorTb}h{tpfMYX3xI0DNtC!j z8g?iA$eChNcwd|Id1!m~b2OZ20j0D@N&;ux)DFcQSbO?ql#Sv)X`=}%4@72 zD_j@)!U=~adcY>A7ZOBP=TeEr-1*uLqD;@s{qAUSK9HaSNY$X=B{5X2Xu*;*yCCz? zDr%&IG|r%%thd{~}d^-*Ttj~~#hk`Zn(G4@5 z`4&)kAYL}~lwiw(sL@otD22$yg^5S4*AzQSZxQ)`iOK^qcmja52!}Cm+!BYfZ~Klx zXWk@j>ckr&MMrXpbfTWNdI7x6@JUuoIJ=>r+x`MK4PgZ_<=#;anZ7!ZqEj*Iyzr6F z{?vJvmvIKgVt^&$5>PaI<^pg)7l4w$lorwcnOJUI@*h>bV;c_K{Aqn}nZ8K4kv41b z^I&dYrY#Z$uowc#K#DkJq81LBUnS`uCl`4p&IVz1&0hxV>`?Xkue{%eJ2aHg-& zf7ZzjU8gtAOY@YM{fUThW&|aQb~tLIoU0Rcpky@{RhwJjGKEBP(buJelKC>enPeAA zmxlc9iLc^w_TA4|u|E79pBsa8P#A?QlWsXJLie)+BEFFmnZ&V0p(Av=yO{9ezoJrq9eU)b=gV|7UU-!5-O#oC+z4ZAXVJi5)_^=fF=|a{b6`pM zAbQ0B`G7?5if1Zmhw~AAW)PIjHfmFDO12xcR@9I6nM^vMfra3!fzo#dF+QTeMg<6% ziP$r0E5J1pwi`9Wi87Qh=^HK=q;zpo1LlFZ%Ir^6D<^sbw`0R{chdESG5yiJxEaL`1YEmSOsRupacIv_$s_VyOK#OWcvwDMSr8;^g6 zHtg7(c^1XN|8?KK${%51aYl|W@(bM~^dN#ak&x?W%E_i0zm(w5n!|yvhnAA+xtlYiMYrNh2BlpJEFWjomt84>Cm@OQG%A?4zm$+We2yEl5fB~4_K#6i$Y(OZfk z1?y5lGsNVXY(nub%y$*y{ z92)Vle$Hm2(C8vMs5Hz4C^15OBHT zAbtd^52aqHNX7^{1Iupb3$^lbh9!G^Pl{?oP+2g*d(?7nh_yMWIP;nR zSTuXOk~s86Si+XelqKI9|H`I8{Rx?ovx)H-+zHUSsO39`?=g`M#@u1!K6K4R0XnUc z?sriU-n}6|jjX;`y+`r;l3#hE+mJNwAsmm5S`d+T_fjv8^GPmgeclNRW&>G05^A#w zCjyo3ayUq!RMi0D{GAmBu%|-eOdHWN(i^u@H3~S0CzIq^L|3EdRY79ulw=y%UGho& zrb?6L4PJ;$DxZBn&2shMOei`gPK76tB3_s|Zm9xAxVu5jkbuna&j-07^q=a68AfJW zH#cfGOw@`{V77#;cd*9Lzhc;NV7^LGRs|x#$}ogq4+5Qxt_hbwFP=q;dT|OO8yxaR zH1AH{BV}h)*&|O)cj$;@H)m@U44=1w?=};eB5xz>9ngUXVix~O7wI#*}-dvrQ9*v!g2^(EsgCHIiw&dKcKIP=nEVWuw*2@;VPn-{5ttmp>4IefTEVf#w;SO^QQv-%Xg8T^>;UFW@;0caw_H-__i^`*>Qa|Ti+FuU$!!oM z62@DEgu(VbcpvjAyUoPa3t+SOJp$5xZ^zfRuVn2^7YHn3*amyk4yeMSy(1akaZ!Q- z+a&t)qW4aH!4l~d+)YgO0@WQFUuJ2M;38Wks*Y>}cVx?3M@gvmJ^XCW{*iJMhBJ6? zCxUS2)(Z?~Lyu_^SPmhWgo>NPp=)YSvqR+`>NBH379oY%40LAR>% zhL7RB?YIxbu;<(z^w5(d1nMwJl*&n#W5hk9$~jC!5Vh3$Ns^xsJg}2f!XbzApO<0e zicNe2L&5x4j0XECzWOaNZ}iXYTY$UcAK4EI@W)9H?4g&gjHrLCDSXa7v1U*3><8O7 z(J14zFrXX-l}(+lh(-OWEOo;wwv%LJp2E7!8-c=SxFX!TLNcUy`N&3<2eECVU!eX| zvvKj)+PO9@*<6@OHiq*sMm>r8E+x&cl^a~!9d%(~Ie6)@1yq2vRMrs6&yZ6ZWZ$I& z-DFR&mH-F=eK#xjmJ*(@j*M#?Kr-w)41Hnr6O-QKuUCY-7Khlnn%V(i19QOgeuQtO zx*jne@dJ)6VC4okUxl`!$|5#o+dZf?huNy< z;9oD$EOQ@}VnuYGA#q`x^$)&iP-g}oB41jvKcWnnlAt<);(=&yTCYj4{qeN!fH~Hh z-Q&*@ze0@^eJYp$h%*~N1}d6uB?I|Jih&Lbx_&zoXuPPO*vHc_n27`u_KpGP$evtp z8&piFEdlvXkUL2}Iy!ZTGhvU4&pjbdvyq#~)}}LFY%Z6CMGqi<;dh^vox?Clsx$?A zlU>LdHLPCKe@QC1yg0a`uwHn|9Np8PLI_wS#ijI*dj6b48P)Lk`q8}MyhJll(9e0;g))SRFZ)M-cE=f2yv+l@e1Nh&=UY22ik+UQXiEQ&DnnpTB;Gwq zsJggg6FJJnPztD<@ck3r1&afwvXSiM>Ui;PSn-(-9w`H_HU(Ct2Y?i&n3wW~&5ExY z@TE@3+FPUdGw~id=}ABb-_GGQ`GeI`2Ec)ws(Yh$*B^&{DN;<&BX2e-`j)whmpMK9 zO{N3kG@H42mD)^Hn@D^~^~!}~NkxgYb<1HLbbXxR4`>6a;A@hn~zjU})1T$iQ}xZkmz}%(YagSE!RkH;TX|imzjI zYp@~^1_4OZuSm(H?(PDp&3c)lz_@SNW^q2pLm-Uxl(%%Too_?dZ-qQ#E6ti{)ez zpEa}0ipl8Tu*VL<9z^1_=2*K9DsB=mOR*6#4*)ZQ`~Zn&a19#_gq3zvZrJG8(}rW2 z*HDtfZ_-vr64f0tJZ}W5c~jH@smKLTa;!?;^^qovGeHGFhdJ6oG~+lKV5L2IjknD> zf-R5J!rh*k z0!`Feco;z_$_CEO(68>E;_w^#NR)oOkG^rh3Xx;1^!be;nJcg~mk+b}X+lV3NLs znEZ*gB%UZTwXh8b=1lu?+?JI|-yhxU%ITh@$Lbi5sGR}~WKF&3PRT!+8_|8d*>!x` zUom8F_sv(k3%(-N6v>gEvPutSFUs<;Z(HI>j@DP-jG?#}#|kR&zt$_Tvs&-f%U_#) z!vIfFvpilT`xUGSExN7uT8IpHI<{wEky(Wpw}S0V^>j7)M{Uz};T^J#q{mu-Eh+US z<}g&w;tYQKV%mkwy(2>;ma%m)kw0r8OWOA=M=Zi`!`hs{_qat1ph()=pzyJp!TOB z+;WuH`NO!NGc?!eta}yip3C0mp5=(4uj^GXF+3cw^7u@e%f1c0;e=%1xwi*US1Fxn zqE1vcVc5CcfNbHmnpT^yrD!_@q<@sFqP9%BtQrwp8?75UA>%43Wi|s|_{5QEL9KJJ z_rHey7&!b*3uYPqT^`^~<`zWeXgq)mv_Zc&daash3cORZ$2WFxMN9CAStZtf!o5J} zzrRt{9w9TZ3o%-JV{ffEwX(V&Kr39nAXv52aGO7gzps7bTU?{6n$m&_|C{?KiTPfy zBmGy0M&hh22Q0GXYRg1XH%QyLkp=iYF=Wl8$z5W$T}MnSQ;(a0*&0G^LdneCEI$v6 zazM$Ko)k@=V;Z_HMdpC$KAnJEE*sj}?B}y7s1xMzH@i zUx>Blt|NHYd*FY7B{ETbz%qi8Leq3F>g$u;C)g<8ve*MG!M`hwq9f&o3T}|-qUo3r z8)6TngI98X#$!kTCu!C`Gs;;pMN{geRZ%%r?cBEy?*sOtrd+QDh$HnL-R|AAa(iuW zi@9>s<*^9QHI`m$ zuSe?SOIQx9UB-vBhOeF2(XTcyny9Cnj;C|C9*GKSudPU500ITm5c|Y*hfB7-qe62fzqlzbSR)GGd2WKn>w%bmUZliX31%VQ+DX8j%LHgUz z#3i+LF#I=%6{lc?dOi+ne34WEgKR#7b@(ZRnbx9mf`Ga-kods9B`lCb`-56%2^;bH zoOeYr-naI9LA?NZlj?Ah-v_`(>ztYD9uWl%T1Mx9Idb1i zyf)_xq7rvpK-Tse&%rjd7OS-veUOWA5E#rZPHE)EQ81#%tSl#j{vS;IRz3Mu(x2rU zXK}WPM;`N7z;!a}nX%a%H=M@NO>WcvSd{0t%AS^oX6ceekCoHKg-&zx&YDDW`Ot~W z`-AhPdQriVNCrAf+>gd;&4mE-;qEIi6BPsv!3wdC4fH1XAFvZ-nDw8>Zv9<@4*v<28M$*}5>1C>_lPgqlR(`&h4I@=ef#L{Ch`;mgS(pxfGd>YV zI4Lo%IGqDZ3sA*&_d)wkSlH`|81cHvwm$wQz5?_}AMzdCLhpiMk#d&U^YLq>uM?X@mWzUO>6?3~7@tz=o6Z9yHzvO!FU)@14%g28G!I-V3EyqH zbt3etlBJv%XyAI$X-S|FQ+kFaMl{{kb*#@RY}Y^mQsfM~w@mO>q#gH6aIb6wI?&dX z278QZxVby9n&S{7ka( zc&JocrPr2+Unh-~-Ch!mN4Ax2v6cyy{lT$rwg`J zbD6BW+;9JA9!Ds5?mP~=kTxbvH}6Yz%^4}DB)@*)a&h}(ACkN1FX}xWiOONuoX#%S z&|LPUcZrltpYz@r;FF9@fo?9(=@azkd7v~o$M$d<{pE1Td8c2vU(1hJ%rHkzbaup?mQs-b{`atd4G2K!X+38s5(eg>v>5Zf0l z&gGU!pxZ_j7*N#B?{n8xv=$5$XNl}CJw_)MEf6;RSd|*6*qvu$yH3gxnZFpGAa%@z z@>xmA1|YovpCwWvj~uq3X7ZOs^MQfS80+n+1&tis5{cxDy9&o(veY-KP8P4dbs?Mx4+&d@kH%Bka56(#&Ok&;J~-Ot^j>Y#76T2dVt4<}x!qWG%u*A$7*AXc3`n*f zdKH@wkI2*SgNrIO8>KSdvy67j`*4T;r4IvajuhL;HGGDQIWXWYE&ii=&u|U}sn1l| zCSYg+ro>ju&^u29q}pP#4?aYBx54VBJ#ov(ls<;mak$36t5_Gg>`0 zvBsS{%T(+9kE%S=a9WmZE0FpNy*kV5(L0~cIPso3DUEXgqXj;XVug2rrmF_pqW{-1 z1?>vOTFY|m#=S%?9-xpUp0n_^?a)99jef~3BsGBzjskm}d_jJ&=xV_<0UUeLnUcJg zaU*;az7lolxcEVN&Mc@+V%E?nIB3K!kv^y%$)W-L(3es?`Q~)I5zlfB_e!~UE75*r zP&SaD*n$WH|MN71uR_RYqYc2MVtj^z*58c%E5t^ zN2gq)E)oyq?kYdyeEK+(G5}c9U9}-e^Y~fw_Z|@|>Y!E2ux}Lq)~g{{Vmxuez}RG; zP-+KXE3K?LZL$PtYrJ#5I8?;5o&$zN@lU_mDHpu184J(eh7=Q7Rmuf?STDcfL`U#0 z4!bZwCW*?p@uLxz-u1^oR4g>>#P_M)?>ih`ZBC!U;pi3+njqN-O%cc)pD~ED?A~_o zJ5Q+haJRLfEBj}4t<9)vX8jYeGj{@=BeoJa=NGH+upF9r=){oFyZw}5UYE}AP1}iW z_e?g1H_mfQyRgLlH6#aUJ`2=BTK3SQw$Ayfg}|nLZ1=W*Yd)9bFubKcB04@bLI^7@ zzp0<djucj5(a|Eb0w1&#lj+cDwL8p zQElgjK93`Lcf(yqEj%QBs_svp)s-$;53&xIS>|2AdDw#^UgBx33S1Y~ME#<;S!O6~ zYw~%CJ`$x)&z@|{FN7C1qt1V282Y~hY+^_ZpNLM6B!shq1F<#KNhqxiB!K0 z#pY2i34G68479>jU@mW<^|?qU$VM@$(bf~u|B9H=gn3G1B;N7qpO?lrc1(klUd0BF zKMi?Dp+#&uQ4$hfkmbXy6$^IJ)qFoHiu<-O6YO~3pf3hGj|4<6LiB}WaN(Wl7zgd# z50t#_<3C|K;^DgAS}2l)5=PPc*}bcKmd{2G`F;?z0sNF1pN{sWIMeY(a^6{{b4W6t zc#sSF?g16@Ubu82CDg)MmVZAs!I)~!e&4EcWP;#KEHn}t#YzLg+z||MNoW>z4IXggIIsPVrm(VHzFCWz7R`G) zpUYTrP#KU#%~H&*CuV_@vomcGEjxxo*o3U+nh7SRnQ&IQ9L6;H0bzxn z2{6?YLmA$zH8&6DLUaJ}oV}f>8tU?vH9BLm?UTHY%Em0N+W0}X&c*e8)w1%{sMc%F z4tH)`_YEo%9eiy)|BNEZFf+G3GM~4pL-0Tv=K8JGptYN?EF{qY zY706L2&WX<$$KuNr?xJlS}EOAhs%PBlp%kohP^QLFQtpM+{@&8mhlN9JPHRQ651-) zcD%Z@91&fdksUX@@#W0}|QcfW@?^t@-LeoB$UGe&2=W zmxlVAevZSRK=Z+ezo7{(soh}0zBVHjE5J4u3&mfl4yj>g8R_dXSFGvD>TxuVQ#x)C*TEgA--AI8G$mb)4bQKD@;yFkK5j z{f3r%`ZA3T1U#%fh3|jwi&&s<&Q?QKaZjTdL)JGy5^fVGH;?+~*LK{erLg*`XO1eR z$zQn%^6{Cle#LzLP(9K@aSQ4lutnyDN@OVI>fqoYbskAk^rkrn8!$49-?1wk%>x9| z4YQM^wC0)rD zL+er}B#HW!y?b8&DDUqq^$J5?t%beTnT=A~3cvu}sVH0%N*nz>a$|m+0(h=k*6Vja zr-LFuU8(}|d)ekd4qQu3*#D-el~T71SVA~^YFkYsU{WC-3;sF|+RoTAc*vQ7aHS2WU`Mb&2+Hs1KQ4Eh$ zovUM8Rp;PS2}|ZYN*$Xj60T~mtYcna@j0uKSgOk$7+tZIE2eq$IT57!)M{UKlVdjO zzE5m3tEJ5(5^Grr8)ONx>?p18bh%Nd)bYEpa4jKE%A87UP4C5`iMG8DtHX+M=E zHv0-yoL-i^)ASj1-;0ahB6+Y$*pgCbT*L-B$yYLHja6&%#~nqqlDyG67mVy4*9?!X zOU3ulWUlU)mctbz9RU@wP|K1HpPHZ?do!1lqJ}`-FhCPCO{4bfswpv#m|g{KAd&Z~ z>!2NLX!`{E?{On7EWFB8p+bS;?x%5{3$f`3lY+nf*N6?D>GZxFFQ-#$?dzP}zy*lu zytIhI-?F3=W}xBK9Zk3FV~bb)3#{jq$TFy;p#HBH@vHQt=Z!Zia&-e?qTul<%(roHlX~n1DwEtKuk~u{44KRTN{lCFkK3 zqQLDVB}{h39_Wq5)X9c3<8~Xm&e&Sg63M*_p(3dw-6F6Cswr+tD=RZydLUn;o^e@w z>XlZbUUUz#Gh^)>9+%!X8$`_OXYOcDmb81?OJg`;?(=0%dO5-YF6Ij9HO;Dk$&6IP z|Dg@~!;gRd{MYY)`swA%k1t<-_};_2m2qJ3uTtd+F`kU+>aL@^Y2yR^S?WAK;J5zn zVSIzuUN|D8D}IyCh(6m0N@WX(vWjDbn@{!P9!9OgLF8Iu(AGTi7@PoaSbLZ!?GI-6 zAKlr;ezFt8eJ`QdN)qd7^#R{+s+T?MF5ftniI9X|%zAe|UHcJ%ez;Ra=^p-| zle6NOq1bs8xPb~YC3Rn{pSQ3FEW3?q9^C-ya*Fjj1CYh!B`~I1u1mek-=>vijhGYn zs$FBTvet94`*9lTXP>@}NTd2b<%l?bD*I*ZapQIvFEofQxzIFU>|Fg!T;|A%3ibO2 zk1IM%46M}SXAzdOB@)s(?FYkz!~Q448kiP_X9-~a@__{iLYMECANESHZ5M?t)fTG&2Q#P3jz zIN5vKOmE^j(s-ew9?4(TF~HlP>ZI&MqE{yuxxmy{;!4s59}ucBfon4nbOKEZ(ZQD- z)kh1@m?fI9pCDltUQeM++Mlw27Ev8woJhgCS$E8r^`d=G!oX|B8FB_*J;LZnh4v@c z&5}t$x1j>Ub_TL(=h~;3(fWIr9inj`nHtI#ol)gQ+#fqQl`|#Hr0W$K!~W?`xsMvgQ8#9bC9$G7}GhoA63WL)7_8HmS)MZQ&F3pI6ZYkUX^v ziyLT5y%5x(%)==eW?_Pc-oyoq1k%Y@l)N}D!6cq8$E6{+R6P-hsJ=?r-dh{x{LQ_p zUufMuV9v?_{mv;RbCzaI)X=TAgVOWQ{etV5&v2!qqF4tRsfl?DV4KSi@nTms$dN@C*RMOgzS_M z08Ch$qQK|7@S1xswXd%qNPmQ7SGXk~JmfTVG?R4Jb~GuvcM96=4iI58r{}a~Y(($c~#CM|R9PrN1p0Nf5g>sWOzA z5$VDujl}Jt+GOlhNs=u=%a*d^@>A(o3)OWGA7Gjg$c3rG;t76{w(!8~jZl<%hs7{Uwiy5kY2h6IMpz)0{oK%}Y5 zzl#28GV#KFTT?@^*o?1t!*LTwPWhkYIK8QUZKQt)X<_(pWmOM-DN^7un-5S}&wYW) zpG$eEc-cMgtR@b&)34{KWEXgXmEHShN+>`C5%q(8Vw*~^5p2HLj1{)xogmqil5e=O z38q~2!G-iWjflbf(tA;U!-w|>Kc6m~wMe&UgAi2!hSp`PIX;*FUFd9G`Wt0`Q|=Ov zuHtD>w@fX=#ogLI+k$fYi%0H_ExTbI>y)muz8$QtEL{t3l72CbW-E|ryb z>}Bu5OdX|}BnGM+7t=_tz3FoRULhzJpcJ7sCM_MN8M`)m-2o$=zK>((LT{gQTHW_X z-MN^H6p37my7p;-*y6~G8d7K~dc4zcL&k{{56iDG$J9|s6Bs7;j7^jmug-`Day=+9 zsUhUNRhAaZ$Da64sn?J-ujOxnkB=8jLFH}G%<&uM8@OuPHp;MzYgv%&kUxqE9g;3c zAK8qxb(9VQ+^=WW=9ZbJO>gW4tO=O6^FA`%G`h}g+T3j2R{*ELAmUbZJc{*xR%=So z_yp}11BYqP(xjWX`&8!Ct`ys26HQ8=fo2lsieNG4Y&`l&j=pq#L^Yx3gnw2uv!nSH!MzNl{m$2r&$(|31{pRedaMA9rs_iWYQhttKIxs9_5)em1L;Id zO4=W52~*qy9#PxbY~{wm{0he=#d9py5@i#}jwHR{S2A9$b;)|^JCE=Oq{xeVu^_L* z2vZ95RZAfLjsd)$$PHe^Ro^(w0v-r+VAdLHt09V|mmt)k z%>>>k)=cA;_i5WB=`ck?YpoX19(rnv;(|!YE9Y*@@2Vxs;`{VzEa^;!F!_3Ss8td( zlRxIEd3R)4oXg+k10>k{LF@+|kGF$6FQc4P!72cJL8J0)PL@V8at1GO zme)&g5^nizQNtX2g2}C1kpTgyH*~LT%onXe#d7tP&-MGSnKk*I=;J_ok*1+Aiew4wh3!kC+?M(* zIJ31II2g?MPUKR)s~Kpj*%REsT^1~x;xm-)f1R|ps#$m=?6FHX-Er~QD{-g7ZJE5i zjnSOXY`HoFORDF8N)uUucR>;}Do26vBrBu3=D&9}nnNTe|6cburdu4(Z|8?mc=ghg4LO)4Lo_MZ-7RG6!0(F@ly+Z-L137NN76*UZD<|j zVp-QUXoOFuhYzGB05>}Y>UXREK=d!NEOZ8q!A&)1@XYK`n&lx57&D1DmmJtKb02!| z#VPH~`>IxMHIxWTh;j@xsYry3$>O8AhLI?IqIgKb_N?dVcln<(kB#>Hs18W1)HBfg zakx_@z5KD`@>KXN>wCYHMpxuW|3(SX)8iJqa>+9fS&gLh^Y;yl|H1~$4OSIk6hZw3 z?AHgY0FS;>fa*mQioa!6jCu*78sN$%qd_y~MMUTxLE(T`j;ey~6 zK(&XkBcCkVt5sxWVPi*ZLxnq!>h^Ssv$jkm`b!P2NXy0^s1J9% z1SykMnA#kNwlTj7?@bw>pt@4+9u4em3is4L7jeUZj5m%Wz*ljw>V$%q=QH8h?C;&1Ea6k6t9=>;vd^)o1DaV@%Wol1L|3R9{<*GuC#>Y zp;nYC;kpF~Dfuv36w5GC`hGP&yCGBf=^#Z_i;=%p9E*4 zA)J#OCoCmZ3;skD1(N8F8pi&P8=;0RN4XjZ-vVZ&sKBHr{oPN7O&^>mT?i1jK6x@x zv(~vkZyzFS#^fQ9T+nT*y9q|{4E(bJ%=1H{eC-EOy$m+1l?rThTdEvPgcNz;5>W*p zR+!lUyMd!^A&%R}Rcx(3A5%T%qtvd+k|BJA-rirFtc=J*#BV!~etr2Uuz7PA z2x#F@@G1>kxS}CDtYsg71k^2z0Li$}GKzrL7AbFGYT}(>((MrmjI&9Y+|YA^xpHI! zfgCuBTAQ2zq-7ilJ519Uqn-st&e5O<5?eyn@}6Ey3C|pxz2?OPw)wHKwlYN!bEwWE z6!gnaZQp?A0UdLIaZ}caNeTOr;m58~wU|X_qEx`j7}hp?w1;#qxKjRw8%1}RJ#FQ8@vcqrNxS)_KeBXpUlp{T*Ci~>I#K5r>K$awQif8&Vy*u zmx*q-sYVK{{vBcm6N}gN56T zqJ%=Mk#t61d5-8<0)Pj|tMayT&&!-S| zHcb4kPD)yo1f5j8dN3{dhRY?e6Em9T#A+_YYkW}$jleBY!d@fd2x9+El5Xh4V)oYn zjknX#VHeV-OWMB19R~+Mz!>5SBrFqk{HTJ9R^()O|>l(szmlho&ifzwW zLcX{KXAF&WyQpuv1W=kN0?PvI7V@YA=1o^)Znu|lz@RDYJ~-Vpdn>*zba24ilxj`c zNoBdemS&}wdZwETt#_pAVW7Y#jU@A!F=BRm=nwc~w8LHl?=%d(;hT8lZ?3$N$@4^{ z#l=g=O3yf6&U3;(uuXN`D+1Y!`v9I(EaLm;B`T$`Zx@%Ce$#x(M1wjp!WoW&=SS4* zX^tZq@I;i7V2+%ThTZQPVCJkR*p%e(A5$rg^FdNwJ_*(qCyJh^Qh{?aBT7}Zs6-w5 zuzQR1rw7-PzbK-;gVqh8ucHt_J{tAF>j(lH)syDusA@HLnO9hL=;|jcOoC?{Cfy)fIjrrETFr->Ez#f`DT%PO|XB z_A(yWe}+>4@tE#&)8u_`3)L6gD@*A?9Z;&)7Ja3;vd^=w)H z5`Ld4hl#^c#68xT#uwJ+1DNQKqA)^YDBUN-wTFzZP2L^VYsKUZ7hI~FjPoh;G)x9v zB{+$H^k4|^8Y;hhlJakP4v~!3C@vmI)q-M){<)&%&_L43BuTFjFH` z{N3nwMV6^!T&>p~)I*y-M9he663`*xiTA-`|D0)oA4GyV0n#COoh8E1PA~1^d%N+* zm^dwWi3Qb^iYoF&%BKi>6^J}&QF9AH$P+qs&lI)?PX{>1rx#jN94j(1=OSpJDpC-s z$TSE}Qo_IX(K6S06FX!fb^iRjW~$9DyuTQnyWydgPl< z$K$4pMrk#Ecu}S8kwUG$QjTPcw=lBwz3_C1om@$GRa)bKJ^Wd8hnkTosbrQ-N4MDM zi;bVZq2r<5dy#G4a^Ko6kx|pYB8Un!w1V{8+(3!4G0aMgjo5uB=pGG_Wh5UNH5%M6 zlF%hm(mb7*Q5z8ZOQaO%RH$+;w#e$`FHc!->;Dth+vmb^v-rcYVdn_>!8>+lW0vt7 z=JXxnq9u9amCG$p2m>4m;4@Kn>-tFP25v{Pe2t*Kal{CrGLVfX}ubW=^uLt zXl}WyPQ`nLdj^c6hSiVD#*E;{yq zwYa~F1I_~sm89HZG|pD$Q2{cX0N$vLiWN#?h?IDxhrn|TAcJ9=NCyV25;aLPA(orW z&UKYgj`Fp?2iE)A9_>ixr&G)EA@D@Ajv2`#@|`4uY6I@bW)Dul3x;Kv5`!({u1)Ql zF|Y-j5i1Sdt5to5tFU8|vd0wd(RC@;H0Zqb81Og;W5=BYC0 z+@sS01;KkewX;wV{X_S;0&K;bL|MxFgi@1gSW`Ms`J%;`FY4i@{~H25E8B~79j8kO zo^ah#chQoCd{+wVj^ppL+i2gh<|7tj{BLI@#(({(*$rC@KK&PsbR)CQn`P~+A$6+a z0>?SrJ3KFUOdIcVh)UVJ$lmbsuT9@XZafFlHok)-+u6kpe}L){|HFSoQ}35w^o1YV z_vFui3foIOW$>fm#2eaxLpNO2lYeSE&MAY8V$yzia+vpw2U2OH9mJ~!XEe?W{u>C+ zqPI(pfAuwAm2c*LezlP%woT`DPmV>Rw*k47@I++lAcZigrN!${3Io2Hd)7}Z^z21#AqC)=_n38|dp%wahoHeHGu z*P;b~wiqSqm(FOPlN!isL~I_(i|EvyK`D4{A0C%UlJ`Q-sjK-GN2&2bGFNZHT4cIdM}oULaQrmcT^zM>^N)~V3< zKF2f8A|>qk7Sj*(VONbZQ{x$OybP?~b@4P5SCnZxey_z69hmql>_;U-{Ei_`(+8K) zh%gz|*<-@PWiL%dU-n_z1#oct+25@KHoo;|P#@=}dy^V&oIyRBq?Uj3bxcdrepYs> z@a1)qj*@OQ`Lfi%zHD@ARKiA{u&quU91rBR@d5LYy)&v5$^$q z5kTv(-6ohj@T*XoI=U>`tCVCK{q+L+7o)ny53b@-oGELlACe$PariamlvKc~E z+S#jB{RgUYIeWd(RZ^gk#%=}Vx@T4;K2}uvP*jpIiZl6p@ZJUXUr8E92$KJ{%D#aK zBnNbxyp9bOsX>kic)veoafD2JjJl=d!KLq`pr_W6&;=RhPt$-%!>we&1!F$6)#294 z{TWWXVcVO64U|N2OI@z&%lk|G!x%+luZW;gMr-2b%4-**N>esK&2Yug_GgkZI%*sb z9Ts#VD@d1%ci=(}L{ZOJs$MCx#Md?BUO*nf7-TQ#sBVebu(a3Tvd58NmS4=<^g00H z;jAvxx7L078mK8yB=QEGrup}zBV~HyHL=c+;PYU_69F#L;s!cCLTo0W$<#?kO8weY zCj51QT;&W4nlIeCV4MO>Hy^zaDaQ94Hi3J=0$pevf>&ukIBzmpot~Od62+5SYkVjB zg!@uxw}Ku3OGAWU39NiB{^Z~Wc>0h?7*E7MZ-2>GgjHa>re`h_cLNr6z!Wx4YtyQG z))ny?ULKU6)J{3_H8#3t^R{!hgxgEPeYjd+R!;)-yWG9yAYP~&I<=tffU_Nxk*rbP|voOCMq28hS2^ z{9~;!PZ;`dL__^%5ljcPVv4>08w)|70fl-m@=**%LbQ-6C@DVShAgU9^BjagjbP$8 z9c!1649HUO2*bl$jjyXiP#7H!y-D7}4FM>n32V^f&vDBR!MA^an0gw{h?g(ypFOUP z1zWNVlG?^zsZ_kSlP>uUEH+D+P#9aZJY3X}wziyRPLiscYRC+9N3W-9j@xXlw}l0= zDLLO3-ZX5D1aiT!c|KxydEt||GE z+0NsDq~4B{w-{-Kf|vALdum<>Iem@J%#_J-7&ko8h_r;J{H`MAV)j zSY)~bUdJS;7_$6j1ZfzoZ7|ezr+K;Zk(*22%^!JU-V9_!quhTXh1?eeBt$#mTtSXkc<;^_Yo;*T0FP(=wIg=7-Z6n`ixGB@X^tN1JAQnQJu-$+y5PXfbOX z*?PC>H4y^?YDLYi5?-WZ8PG)%Chs1gEWVcLhjYsOc) z+0-6(T4#0TVq`=n-j4JnvAt^sPD^1T2r<=X4R8JtvH$ZeNI3?2C=pW&{;sH0SnMU7 z6KR_+eGZ$2G?U>^+xehk5$_9{%3H`LJ~(GQ+aW6 zOSW1DW^MqFUyPX(3bk)049-0cTBA+OdJEXZXb5v2lh&3;-m6Y`c}A31s`V+wUa&l3 zB{NnVIT3PnJ+*Omkg0s5i;ba1{3m4YL7PHw{m046nFUOwLY@tCqXaeXcO{=02WRh> z*1kq*Xyz+fs_=ga06vP%;2$h~)mfspzJ=;0I5f^JJlfqjhI~x5$dgQvYdc9U9J37Q zgd@V9T`U~J!q8|E7qN6CS1;oA=vvgTxeDcM&BwLySqF-UK9%M$0{KbF6{cj`BxEl! ze5Z*~dFK_G+j!~1=#J2VsHB-F+L40;j&1{HOIScsM-%SO;x&;}TYS7~BZoGj8moCt z+z_0dp$>vJDS?x_3QXAYmCyvju#ZOFXG|D7QWaxESdP-Gld#x9-si!?vOTe(-^z?u zNdhVjd^orupk({$9g`o}0dn?m8nr!i#t}0DRkj|)n>kb`es)c)%qX94lTo2Red(Hp zE3*ct@GM5me7KYMtk|Q3Ts7%W$;MNnP-kTu6pRD6t;hugF56g6`F>J6R>>*OMz@al z;iiQd1Py)isuIULTn+9WZ7FF8&G;JvO@lSHOAy3$`E>5-&Zp`40jGGbk{BT%4puir6yLkXDzof27L_s z2~xXpNk}Dip=AiG3P|F{!CrXG3lDfU) zwZCo$YS)DSjUEx|y9UV%NfW8vTY$nO{9rS_nbjdU#Da9JYKkFo*uyLaR96}WLXlM} z_eC7mDwC?fU=WUD#SO91rY@KcV<hM)Jx>r5gxcxCbr^_g%%nB2Jb5r*CfS=M$J72UdACZ(V z- zg(~YyKIO3-UCH8orBOYx%EZV`gF1F=V6*_|Bgja?301u1Jx~`kUznr=Bq>zO<&Bmb z?V9E2v}D9vnAO`Gb?e|TgVHWCxon0_07XE$zeW|4lmQV;D!SbSZAR$xG|{90t@yZ8 zC_s#C+$JuA8KT)F09TlA1?ey+&Zz?&HJI2ERaWI%BGX_4Gry$)F2DSyFGAOYfLxu$<_bc~a=`KrAdQvV9e#WtBdoiGJujo{zNNbue zgbhae94MWT^VaTKsa{PE*N98A0&(w`-9M;|WUWT7P9a{fmq<~8*itlj8&=hkzs^@& zkYlrVO@cMaja}U4%eFmDxp)(>Khg4mw7BP3d=hg+Xa*F8qmC%ZcCcj_brUoDSlW?g z??SR1Kf7k}!^@&b4;5+?rphlaVJzDF=Z=#gu?2u_M=xI59s`(K_{~W=>C7+y@;9KGa-p&tK3a>PX?r1LUt6S`Y}7{G)){>q?gTL0OE8hdl|ntE z@YC6+$Te@DNgQTr{6kgJ%ekPth6W;nLTXv$%JL`?+P0l}_D4cu`LX9op*X}Wgs4n_ zHz@oQJMX^iTbRj=15mI^7%;YZX52QEl3slBN()K%?1pSa_GC{?QM))2AI1NKpge1$ zln=swY3G@eZ&{1SWN7L~EU(c+gKmqoWAj^s+g!XOY-{PgPdZB{wnQMLix+JT-nY#0 z9)bF(4du!hlV6JkG>F;N_T%n)pIThD5^a5%ku+c{;pAniz=&v`;<&mFa=yYRWKik0 zSq>}NmeO?7`-qWS3S(pttxV(y){~M+ROJpPzetS!=b6ZcWP^J3k}IizcD3LW z9xuF$!!8VPmw=lmMsSCk(vnb4wnlj1)b1hnzom9y({UKI4W$2qYHP5}&}$V(rl(Mc z)OPP*)kdijwcQJ3%qY<@bmp`9lXh_3YLqM;8a5&Cz-sbGLa5I=`@Kz2DEzAS&C9MW zSrp;8l9IwPzETq9t8u+#*Ss_F7~V`*fxi&F9~t|Ox93D(Do8!wkiG~eW@YwYyIJTX z>qCen1RG-3H_@uwf_6^A1F$|#>++icG09_dZZY58t~mPe=E?8WR?*(^TL}{oU4}P8 zoj*$U+#tgr%-XkiHleOsrl|{Jy*7mgkZTJMp5(sK!@=A?emjab(D+Y=8e>okcd7E; z%`>X^Wd~~R<)^d>lLRU1-a=>uxM_YBdRpx&v?ojgmw=^G8%=<5XQ z-}cbW&MzvPWx5gRv(p)iP^|UCl_(iP46mxIykiL2F(7_QELNX~=Dk6BMDyjjjY=oSyzMWt|TxPXED zHW=`esk`?#%Y4n0pK&k!S;4ma8b(LrZg$MU5GFj!D=XFEuh!TVsq;sS+Z&v@%uuOf zRAj6No$8e`Dire-jB)kUz?{|OyE>%F{FJ*C4cH_-ZaNQY#ZkK4wR8W(%g0WIclJUx z-6~<9mC5)twUpC>Y$s-y`(OP_n-}i$cyKvwQ77mjXy~R1|M}JrXWxCe16+1UbPLw_ zYH9$bm16f`cHv0i*&onlVcdEUsUxZy`L;eV^v^i?!eX2=0bIWorJ$G8rijG;zX+@-F zIy^3%)W_l-h@~=`6AvYd#maX$mFC8 zQvVimvNB599oS{oUPhz3;gEH+tpP1-sM1=-R=&B^;;Z zwRKU}Lq6#h5B*@y&Rxm@&yUY~X~Nk50_Te#_Xk12*7{6m$wf(LI78L^Z!jw?s>fNu ze<7Lp*?g+ktK<<4>}5cjb$mH!oOF{f2Bb!HIq-*vVLKw$S1iC0uhWqnPK;>9rQ5X) z{x!2=mR`R+P9IvnNX|ngqif4_Pm@Amz4DRpi!F1MFUn}AA%v>8UwLa^~Pzoq>-=52Np>&FN7_(IzmF4uf5k?pDojrc;_>2j(K=yHy8Y32HgPohBX zpu8SaI&$ULSL+X2PQH*~3C&7o1U*QL0zSvahmN)gRa(K=%$`=#L`JW4=Opd`(a0nfmVEXTNmvbAO z2;&NZT2`(v{UbNHj3+X~(v#D4#~v>^+WV45g#(BLC)&q!ujv@xNHte475!w^fo825 zzop!i!KcSl&!Y?U6%6iPxY9v%0oWvx`86-v*rqnsC^V)Wg6;Kt6+O>S9h^*2i+yX3 z+WL=6xEM=nMc(>`2Y6!0l^aRR%&wHa1zNrA1Vb$I;oPW?a7XL9oUfR*0waOTNq0uJ z!py?tCuFvMgGj$PsZ10CN_W~A9H*K{V>icMvo)##mp%a@|BXM(?C<3fi}#5^^6>F= zvJ2-=&$dLVKlE1WyJnxt5$^v*!d%KW?$*veBTk~8J%EcV1dve^c)q6C8iq<8Q)SilCIREEOD%uWSgmOBu&+_qe8L#yc7Y?=1OuD(AgG}J z(P2kQo4ac}24B2p+ME}2co3QV05o6_(5~O9owwC+4 z_I^yrx+SF)kgBp=I3K9y0=?IE+;`kaaFP;HmBHu+g%6`Zz8eQHWEb$;{i<_&hE!(i z!&(%iFPe)0h2* z&eD{%L!fY;FLC)(0Fc+FQ?0c4=tu95EA53rJf*ED;*{%9IM>auzH*;Sm+hQ%`0_K` z50@>r`hcRS{y|CsKH{w@{X2DzrNQ=|LME(3wjld(HAHW&SRs%;=w?rGmnrHQ?S%yK zaBhB46}TCib^l!=&~XZgL;BJ|DGpW)t}m6cKsMD*~jZ zSE25jkxn3fuQCEkj{@NI`u6DzT+MGriE+b_0NxJy+&`M>Oex(#8zwK^7v@?3u8Yrr zQR>%7^h+MEoEtp2K`T)5qIOmCk@z)y*U>21B4QFZe038%3_(fh>%56RMpjP3Kq%5A zu^vyDQ31jPf5C9slb`hp-3gC(qLGf&BR9?&kSIYE$$}+ql1vW^VYT&ys1lh7Kj`1r zDe@B1)AUg?O`^I#d@+gIS8y5!z(wyv@-5neB%!5Xmn`K{p2;9}?Ofg2<#d_TQZkua zoD1+-&JW5tt&?(Y{$yftZ7yRuJMNiaq~n@;4>3}kSrR{>kukW!sqC&C0JZ_(Vq}g( zp3TGQZc@=@T$8I)YSLc6S|~}aYQ%OMxU(!LB4zOqzXXL0CZ7q3kfJo_sfmfk+|0#v zXdvzv(hbPIG!dM|*vF*hIBc_HhRY6spi;!%Y2tR8|Idc{&{dJlap09q2A>)@yDT#3 z*D#3M5gB&VC(&(v!#0qW2Uz$R`u#E|gTgOAgO~Gg3XzNUpy)p7{an(dsR|)5lcgk{Q6nmR~I|F-qS_vhq32G13 z^^SX%Qji%!tT}Pn5XE*mujkax&VipG{S5mKYD4-y z9v7+gAK527%Fg-c(OT3DkBpiea4%+pK;s_t;gN1I+*J>L`0>x5|N8w;KfQeU@#V`8 z-)H?*qQ%q-G%Svtv8;iG8Ae(uXV<6}&*{;^v5_~jhl;Wzl zH9)bvf%Zd3F?fTi;wf&1-xrVSAc%MYT*9C;N-?X=MnKPB{zt8j z0A(KZnkT0rP)|8nBC9f)uk6=hBW4voyztU12gHzPf&BO29wubCv<&aJqjchNpU`zb z)i+<b^^^HFKEGFW|dC>xAOOmKuaU0>600V=( z7jBy89>WDB=;SxU(FTsc)EbPh7NjCJq8HOU9zhxq+^5nZUTxy)(13sR?c@MrnU9x# zpspdVkp^64?27skiJ{Zc76Gi4A?Wgam(ghlKzv5o3oy-c*sZ{t&MDXP42b@A#A~O` zGD|)sLS9k`>fcBhBWXP%sS@s3PL%<}EQ2vA*pO67hl{?vMWW&gL*S<{6tUD0`QA)W zT~6~U-8}9`IG!f~`7ALwTo zcLS;kA^NFcr^bY-7Y3458&2QH@+~&N>@;MNAa;XW1loSb04Zwsg zrKhK?{mNR8kj~BRqAm$WBhpI1`0j*%rZJ8KwbF4FF3^8V+a9V712Pun*!p=B0}q7_ zjN?LE__Vhw$vz35k^dZ00JJu=keT>~>-^vmDdK)dts0`rh^k@=gZ^4&6Y9V$R6xhj z7(O6Tq8U9B|4d1J?vM6Or)ruLiYrB%Xm~Yy$C69bqErBRYe(NA*P2PwXS`t8=p%7M zyS6lZ(G@mc1c{_r_vxQy2v7!-%Pr+t8szX7X*sFYFpG{M6hWBFgF z_i-){Ba;j9R5VO;UNrXW2iE@URmn_=q%;fA4wAYl#u)AQH7x`l50bHjz~1tq)|Wsx z0AcYr>NST^&wh4%=I%~j^)2f81jQYy`&bFjji>VD2BtEoOykbZyrzejc8rFm{W1sT`_2m{J=#;zF890Q;LUM7wf z;bh|`X%X>P&q_{~$HZWM15LT&wqhu$Dt_^W(no3fPp1fbi98uyZKZ`1C_XL6vRK@3 zyFJCLoTPSCOe5`0r~&<=b-Y`D?jiBr-PlEfWNnTU$X#~D)sJiob+G(|J-Y6qv6HQE z^NqGXLrjiPeB*;RY+*`l@?{BQH z0K~FLQsvz!X%OiH#oZdP$*Zau@t&RHrC%e*K2rD+9&5D;FvMKmhc2$GL2z4$ntiks zEEnfaO5itv%WIvCi~2Q|n3xErO=;)ucHuk+iI|-X*&XsvPLe2V4cYYDZ*pt4mk#zg zpQ{B_$RLVDh(hKYvG)A=;y9fa5k{T8?cW%Qk~)lf4sOaWN_p`-JSE%fm3B0jLq}cN zs;&f|x=4lwWK|@kKa>uV`+D73Asd5RUf+WDvj`tfz%E}cAY)maI!hD;gkfB zYz=ubi+j3CIES>6IAGov1u3Z_yLkt;QR^1dmfXPei@0x)>(t$}go7JC8)YFJ?uzpZ zv-xd|q@;=50{^I9s&=kf0z~U-?R@2R)0>5{!Us1ipI!Y5xHekIdDIKm%nPT@BziI` z{FO8)6BPefLXoIU14WuoY>5i>usRlGNSEzge72pbN&W9Zn8tuiFiAc`-Zl!wJrVDL zfNf^(Z0lE)yb0tKmDs2t3>fGml8yovTvsjR%y_0&-=ADbM`fNzKPV-nTj)2?N*Rd+ zXRdNrv2-0TjC3M>NAsJo&P8E&fzEciu^_l2$dJCbf2Rg)X5%J!eUhiCp(GD%K^RDC&IDK~{nXC`QMof6F`z`GF{{ zO1&U6LeC=C=?5kF5r=bn)#1`P*>D-{M4gri)70N7n%l)%94|EfKKFt9a@{+(-cpn_a*^}FZ%Om8+n)J+!V z*F+AIO)i~_eRfsM3mpg+uYv4TDvuF~i#I{;hu#1=kz`6=F?XU{F!_o#;Q6Hs$u_Bo z@064!>`Dfku@N(eu`^m#KM^gC8ad9cce!R%lvh6$Q%h0}uf4T@PiE>%NBp(F$DH*X zekGH2jq^~aKKviNr+*#Nk4h?cW~OI6KcyO^6QX&FtJ%x2kNv=ZTwzZekS@?y{I()U zxm#iWSNVz6UwSUWB5|4z;{|a6ZmHFd%4kM-j5B<5LVK&0p*KIdkklO~A0SsyB=fjc z4o|aD0E&n~7~-!R?epUwy2ym_86T)IPJV+fbSx&K=7KHlnZUE}uf4E>D{a)^?lEl= zd@p0!b;-|ja2uWzMlrS=+QWgO?GA0X7{~&^rC#=9ORfGy@nMe;{xQYJjk3JMm+L|C zLfHUArLJt;W3|r;_SK8j9iS#mGsRgayv9HDF+JlEC%S7ICxRkzk(~o+!@>&Sjga`i zW_F@&fOR7Yw=+u|FoFk&AHaXDD(saBhyTH8GNxLZ)_J@6OYm1XqRLR`D zHrpn1R#gG^{1%AAA$LlqyZ+>v64K0};eLx0h?o-_InBw@N4+(nPbM`>Z|xqO&xvCx zGu}_G1BYDDF4C!cY1c&k+A3DM7GW{eh|-U!5`qT>4$5fi@eU@3l8WYJ@2x5+H;79A z!jdcEx03F;2a2W0@;ysO$vsw^kKZhIip$M~9R1U_@HDVTLHAxq`ir8gE8tpCeJ(Ek-AN|wd1 zAkxyhSSLHxT1vz>6X63`a4#~d@f_w$)HtWPnJ)~!g3(l$iujU6m+|lWYrt0D6XyWl zz%gpQN8#IGry+HHAVvR)2(mo;83pMOkA|rlT@%!!sC|teZ=$-Zc~oQ~@Lr!@*tckV)Y>OX>V5wKoMRPWwI2{c~a z5O9SL7mBlF)P~ z>+Fy}_KlAlJc=)u*RI%p=ikwAxO~V(o4u>}8O~e3?)#P!P!Dl1tKF#uwPh~ImkDEP zZUN9CTjA;@OR+P3qy$1MV$Bn@oK8Mv(nnyyO%4o)EMc#hi}FybNV&61{>;%)sSOP~ z$MWQ8r3ihP=iyXL%ry4g{nSps23|KIj{|u~+BxB_**vYinq8s)zn5<-bgLyPQMz4z zb85tTt7NF*ErThe;VJpW=P7K?gTu^L%FQ|v4 zs>_;nw|31q-mq2VF-b#)=G`ySG^YzqFSUZ#qjZPCcp?^V!9@w!)X0TSF$Kxlq&RW1 zMDLGblM}yX8e^uFB#hf~?y9oZ2#S2kP@>a%Y>1n~>?QzgtMG;Tz;_a@$eXTMuJ#)U zZwK3uh;GE5y0jfYJZplg7p68T5~n&F(8{Sz$hEh$!E~#xmp;mj0c}8d@sFdpS(q79>2z+}>;@`k zxE0DynJ){^3`QCBt@py zr^FxZqP1e%;nP&3b9j9D?xnI7Lcik7%zeclyUNdATG%Yk=g}Znu0CWGjw#!968_By zw4Ez6`KVO+zgb>r#00$PVKYe~S z`GkdPu80zG9*2I2^GbL|*AgmPJooU<*|hY}S2V%hk`|UB!*X^YL=+oo^YiQ1A}wtt z#ngZ@I=rz4Qx7^XAzd8j6OJW`jOs->*% zz@AXzhj<}M4XqSC9~Hk9H9D2KAt_4sBDh|tB5RdB$u(8S`motWR>uOrC50@jT)luJ ztqFfwH(~wyqOOOLvQwFfd_4+NkQlnpq>&2%UAEv{AC{7&Q>_P{YT7^* zYa^@J8H2&^atLd&YNsmV?-;#0Wz9VhteV=9R3z{TxaRm_i|~m%c3;wR`S}vfKS``| zC6Hd2i`1@%OQfgT{-Y*|0v6Hv*dS+gIr_D`uA2?&UO%MSuRis-6JQOH%=L6wZUtOB z*Dp!>SRQNjaENskHmk#FXURTUNX@Th{(ZHs_7MloU55k8e#a-5B!-tc_o;5}ZX{Ll z=wmjuZzj}|m-Cb#rE6FY2Jv2~BT(jy8h_p8;n6YKU*WtNo2R&cCI}mVr!6uZ9EyRI z?y~3i_JY7foRXmZ#Zcds{(R5%)@n*2oii*t{1XD=3xhv4<0(C7Lb}Yi#+y#fUYx-P zxz$MB^T1kVOI3!M%=s|KQ;$0Askr^~MSiV;A*7;iEvxR~?zoiLv?Yz&99I&*Uh;wg z-5{kML1CmS&K|J#ZUHaqQPj7jN~dCWR7Cl6T}98EOXjBuXz}y`&zOm z56F3vt|i`hD`OVhH(w ziImx7x4u8N`mZWsdYfc_D+tpUw0p+^-qxZEn%tn6c6EFg|}+=*$_W zIUgLid4=0fyF&e_aL>lEDX-W;vmd*8kx|~81zfS_9B@j+x zl%RE;qcqggGQ?O;5o~$c+%_orQy#PDH9St-a$8&h^I15TjZr)Y#&K$w`CdL8CVLTH z7Lq~xpF-5GFB(EjPB6%E|t?8!(|OwWyBAL$6UMRymqu+GLF_@ zlsJ1=xDifj|(c^bWaTsW7$Zcd$|1cmzd!E;W3%it2zsd%k2rYOGNg zbLHZnUOtnOMUujqKPLy!e2%x7lNe7{EuPx<8@A}R3Led4Cd|?$$N{FRoMsXL$7@tt zPXoJSd_jY!VIl=wa9X0_3p2=7-j@X$N%a4jn*DX&5E>m2oR@sE?KMllBF)s(%Feu4Xd)l_x>6&fPc|W+()Bz;h?-tC zE?-FCN%Ce{0%hYYC)TcW-ozrCT3;rEeC;GtW^+*jN z)QdtpKMm-QG9_*t-U44(4FmH-=sV!HN@sAl_-n8ozy#f4Dn4!JMjvn$`XQ5CS7Bn7Rz@2PtcelfD@gElc|Amh0;EsNc`#7{sdPTll_P8&#H@NfdfOm z?IJ0{_UFZVqTdUMK_$dj)(R~-2o@`R$l|v%dL|fCQ^&q1DFadPXRlI9vn5qm?^~%U z#z!#syq%T^O8FirV?gARXukFvg%%Gx2qV_yZ#>O2G8S-JCJ^Z^G z@G1mlg2t4YWF;URd}9xRANB!?2v5vX*sc{GXB0dqOj<&%3Qp_3#d|N8(q%g=YRegD zhh`AQMaSD5?W0y9JF;Q)24{J2_FjpeSKpo|0Q1DlQXeEy+gI^~Tcn;p1nSyM+~^Iv zTc&F$0^pg|`otPpA1=Ke-}QU(6l8oGLbB#ZrLsANM`8wcMEK7|=WOQmEa_FGiulQV zibj2|rgzmytFlIBJ8myB4oHKXQXG8QZLq|2V?ZG%z*E*S$Ruw&Z&rOuQ2C45r4SqM z!<94l3^U!XY00h2xyqdgoSdc~B#Az#$u|yH%G*o5CA5lMkx|7ldGOxeb4Ay&9rrl7 zOb^oX&8(#0@D&HDyV1-ZJX^oLDoiMWd(RU*NVRQG=ZiusVk{$LrK#KmhyQkn*E?%q zLAjjpD$Gu@^d3ZGy&2)%6-72^#U|WT^i5YL%bpxDI>n7GB4-=e!CR;a&Tgo!>HdKg z%pKq6fKY*si!GLMUwrZ>yWUSKwt-U^AO_5wz2BN4MwKXW=6{K{rFPk{>3h?yl4;8x zuUdjC$7U+ow((acgl!j!5kdBvoB0D9M$&L$$3DIxU*R^GGlyP|Q1bWSh^8Mn{S4(> zL!G>bp9Nt%L6Cuvabf#bFApeXseVs#&7~F25b#8*$06d1pwS&6$Z0K{7tmToTC!`H&NcP)z6e)n=r1Grg93b8$YSnJC(G!Wd2$|LFu=} z?2B&~!c$UWY~BQxu%c0Rl61In6v@R=d-W{$D|`Cbr*ZD(BZBnReJh)0^3<*;;txD+|ML_bX{DKaepTnd?T{#JFHYc{uszNvSw9|x3 zi4{fWWw|WT=fSc6$(k(TG4PJbXtX$e6G!~4a3l-#zfi{}6xELP*}GWNtO?#lTNg5g zoIe@Jvf?hsIn)D0G|NwET*bw)kdUksuO5kuO(uUf&$csIp1DVr7>prp;~x0FYuH8!e{O>p4=|aTsQGGbl4GmB>knJ6K9Sv_apL9Ba2YeapG;{!)+y|G zI_ZE9tJIeFm3qO&8J&iBL@z%@g{-aapgW5Cy@@BJDfn^kgwq#5&OG+Dg-a>gB7XNH zK}GmWtpOp`8J&!!q)8kZ`D>r*yKqhX>UDs8sY%~xrni7MCXa+~XdSQEK;#7j5#kfd z9ObX1TYLpz+9%)25AZJPeGah#N*B)fIvD$)+2PIljb<@5t%KN4(MbH=@#%SsHyx6; zlF=m!QOm!=aFMp2taP$ICTU40o}geeyhhuX^9fZcfZwFkE%leq9vFvh*8Mk}j{@$Z z6v!jxFYhj6J6tCIitH>7z!eD?C`fVy#jBex$rOciyl+82zb1bLKPAX=?m_tXe~nOS zz|`^OcsZSX&o*rw+|FaLl&&LEIF4>vEAR-bt(N;0HK8cTAQ(qYr}Lh%I1z3)1}^m7 z7&W_RI(8C8V1DV+g?9Wt@7$?Y{I$tj&r`qhmg&gFWHeAX*D+BYQcz_AoVM3jn#*I@ zk0eUj4n7{eZ#?hMh9SES zy|x{qjpJW@vNfykM}3b;J8b7vBy_-*=4YX~FdlmLWDJg|rZ%3o`7$95#0 z-}aNyjcVvm;#f;y`;aBI`>@LMO$~|fSUC8Kw9T&|M#!BOYRNNve-$alf$fW&_mR?E z!j3-BGhlbIsbe&s5{Wk1_Rl&cS7hh92r)(2xF&n+UGyY{q5dI8GHOvbg4u?C9WX3zE&}uE~?rtBuVJF zQG@Zsj;~_1e?XM#8~&}{@8k|Z7#m!dXArz^_niN$(t+ z@l{7~0JS$jUqw6%-kKp2O>?p(#p(Vu)My5sSRFN$d!3g3L!66vWODI*Cerqz)a)=Lg0d{beR$;r7f6)nBDtCug_2{tIS^A4&5mXKo_wI51| zk^=samU@t`gLUC`C#Lt}lA$LE0J=1Gm^RQ$f%dZq_S4YH=syXV2fY|_nGuurhDux& z$T43qY$|w@%n6%rP?;4mI8&h7I3h3U$n(jFHsNw{O&rn~b|!`b+=iW@d~tgQBR7LU zSs7E35$DI6@l@Ty@$9ZIePEB#G=n_6r>_7he$2zl7nOl`_k$%czjniT^VeE(8mqff z6=AEFKMvJy-$KE+Dv;8x3J$o0xO}th1}zN1Q208%EJbLOp1MEyqHhY8Eg?N-xL0_s zwpz5|NWrHXBKYq->0Xo^E`5eY`Bnu<0%_t1BWs0y!`hR2-^OFwJ6BSrrO0F7RN`k6 zC&?pUlS_L`jFTeKyk_szw@0<;ylV8Z!kH=+xJ(d-$H$JK%m^=4`Hk86(nBt5Ff0KE zO(2m3UDaA2mH{BUNusHFI<f?JtrtA1k9`aN}h5)qfqU2PP-F|oV#5cm3sIK}+GuBX2EqR(tUgSnv01>N?Rfz8jdf6)0-QiIK$th=2Iivk`_h8xG;>uMj zJ$0O=U@kdV3!~zBxwFpGylL%kq;WV$^CRcZacweX`ih zP0(84*a8w-t?M_W*$B;wLYC*%je$PWFtEmjTUHcqGn|fKSY-5F;gF3H`C6_THfBiX z5wGZVMI;CWqBy>QO~gzVoq{_RfPZcF&6f6b+a52gQlD566XKL^+7ZerL}&u%y2BOE zK2Ay@E3i zVgrBQh2|Hwp74jy@mx+Y1vBvft4Gm*z9U>EC`pr*IK=ht+4Y0TAJZmkK9jgN=wtZ9 zb!3MPXGo-nOv#+kqA}S@IwgvG#rsTvKa#z+4cDf%V$_Nyhd!dWw%>Qk;{*4gA_nV=?t~&4k+c0t4(5P z`LTWA7P6uSoW2W(*<5I~I?%#r{7i}}eB1fFhYOdW^#wwdhojZv-F0@ic&JJ(yWn<7 zm)>{F?*A;wvuj@oN^vsd5)9-;MH~n-lIeaWaXUsyVM_&bDwT<_ueRm2HG!|pu80N&^2;AD0U{)HJB)AYEvO?? zk5-VA�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

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

-K`>UuRc=JImi{wm*QcVn7FJm4y0`!Fx%Ia@iaElxmwU(Kzg6^E-i&2 z3LwAGL|AIy(e6A2Arw-6iMH}5#fAarGXp*vBJ{`W!jvU_#4QZcuX3vT__`IpSipZB zLRrw#`WzP?_UHClSFP@QJL#fR&q;avobtH4iG_JdS5vhmtc~Rc!0l7 z3~ZS|+6hRL%Yv=z_$ekStft6tAb2~xtg(t5xw|8b1nm_p{W1?45(_B0phD;>;yfU6 zLl<8r1avjx6*h0skboCMmd^z=UMx2qG`9v@Qu= zZyqgqB+985kCCv;93mtNnYk4kp#Uk>h}D4P%x-}lee5SbtYB=JxAj39dgp0!xE@T?MzfoC&ed8wx7(bVS9V1L>bE}Vw{9z1BJk6>V8r<1mv#~$?z>IyFP1L^u?_x zS6Qy*2%L)=ra= zE8ID-8ei(Ht>$z8O53ZYu9kuo-_?E~VFKoURd)N>3RTaqg(kaJ?19tZ17!@K*a?p8 zrR4yv!Ax~5T9EFNm`}sCe6jITDN6>WwlvryCm2IP#&;ztP8o*h8TG?W{c#IX(cE)E zqsQ@9TinklP};A^w5+y?eZ2d49U4LpC+iiS89~~{1Hik-tJThnj=4}&2ctq*H^gVG zZ<_e3g42JhFX|iZY`I}Xa*HgSU7Ncj>GZ2^m(#gT>OB$V>&;?5RxNGUV8G+>H63#4rA){%u z4xT=1E5~8e-x72MM|IxO$ZA=}g%d~Tl_xAnjMdYfC*oV69vFM#&kWzPod9OtbjCQ{ zH6#6-=Pv$zAuoT>MbOkz7@M-w2=Rawv*{^vmW{_v0t6f2TIi|mFUHb)Y+9Dp10hjF zm^%V2D08>83kuxIq2-|!GUW{k2#Ta6MhM#Rz>dZ-#{pcC?s>93j|H6&aFw`&(^k!2Ijn8=?)~!X_i+Xm z(v8==-$;p}@#Nw$>A}!~iyk~alR~QZv)dHBVIL&;JvkRwxD$uvpyolqMTf|dP6h}; zaU;pA)6Ckw#_tporyxkoDkU1E*m8EU#-%u&{y}-Fv;m+@+a0#i@6(2_2WkZ{%?EnBg@ib3+U=CQWc+W7W`#&5tvY>9IeXcg$R6pZoLfP-j^o zeS==vv}Vn6LZ2M_0z+bwB5fUANxUTA0{W@FqunB_R-j8DW#y@kiHzf}%Q539t#@ZM z_B3vAZ)4=Gh!$uHq+jqD`VW=wpyj!JsD3{X`w2&@5z~-no;jX`&IkB;Cxith``Jl5 zKlvkxQWCz0VpxJ-+cHBY?FkX~6;nKRZPNaXH0U3D>NxMj&=xWjZl6p-XQcdpkNv|J z6r`tMsGLBTW$q~|F9xldh6ngNfZvc1`ynZg>dT$Sx!XHxMB#iA%T24ZU?eB|R^i;T zkMyItCBgtRWjHv-Y+&9>y>x}?wQBApwwbSLh~9tEo4!#m<($i6lZVb*3%g=1=wYk1 ztp(TWOC>MnC9w6xP<~c#Jmt6KglUt>SG@afTbTlP^wIiKxtEv++Bl+1aVDvw9KVbu!9$XCVuH z?-X^W&NWUE@4)g9&IjF($%*cdb#+JLhUE4n|fs%h6YQkdy zqfg`u$e0LCcaqVON{IjD19pA))C?&0MA`J}d2Sx;@r}>Q9D%$+cV2L%&Y+{iU1Q!j zcgb}ZaJP7s`+m`hd$n!sVupvy%G99%@$91C7ZXZ3`91M$N=<#%xuK7Bsn&6rKU$ud z*QbNh*DZ&lqD^d!h0a^Ey>%JDn8m$XJpnV?I^tvXxJ&kxQXnpVxwCeBAu1~0y)<12 z$0j#t_hPU`u%0fD?5c?&{w0CNdgRBJXT;q#t1-JZGk&=>QxwQDXSM?LF3 zxoZJsV7_r3CJxDRh7k$sVYv%OkO2w=#lD*LyCO`+mwABdL}kVV_Mov+0%bJB!O9k1 z`ZhKKTl!vi`HhtgS>G6g(2Vc$O6QH?FxQ*}8W>!|s)M7#@m7St3p~cH#@NWz0G)*+ zVIh|5@a~y|&NbB?qJYUDvd&Y*Wx$p7!w@jfD=Q>#uy?d9eKJ)|J8yYZ%b7#WS<8>4 z?cY(+!UK!9h26{3<8>*2y}|nY$eozT{c%1ra8whOFk@o4vdyiF*B^#ug=fM^+kVhb z+xlt3CC}O(jIB6o_60LZd;A^W6?`JgmI%l+mN@F)Xo1|YKFT=2?e`X{T{j`%8E5u` z>FWgx;@)h$SImIjjjPlO2zPqzsGVQ>c6)&{(0|q-SbW{pGk$mb>KjM=Xv7LwFS-#T zFrVw+D~Bx%_4fS2k^eWHm|IAf%83hijRr?yJlth~0ntAD39%4i4BS+d=-C1{-)KHIj$ z*_vH;Bm(Qp?6^K9!8pr!XNzVB!_`hO!5T$(@w(|7?B*;UFID1V@MgUbgZ1kc zZ!m<9N?0qWD954p!geNyKlN?g_s86e3yZX?29&MZ^iuOek0U zqgfS62->CPEJt*#_703fB+mb=C{K_C-#bpFS(sZ3$(TH$fWPYradzEk9*YBe_?lU! z_UU-;0=6Zu$z*yt^O(_MFN%JE#psl&5-LcmsNw!X)<20K!+z%w18RtiA zb3s#%)3mwX#Ot!iIUcM)< z_3}(xHmQHQmuo4VYxnxx^eu;S0$v5GrEqs+QHRK#H1ALZ3Or&t@BIN8;%lK-Tn^(` zO4W4h-P!EIPrylZ-euESua=&ptD5&9*J;v)?t95NJW*#ftT}Z-5Hfe!=%RioQAJN+bX-^j3$Xyo7VImO$BH0 zbM!R#icxQ0WwSLyUXM%8U{Ez;o2Jblq7v*1z_wu%U?kaM^~mAPs>`(5#^~SDrL7s7 zRCMHed+Ja61)aw8VHu^FNdaut=cTurtmwDiXr*7a8jA12uR3XO;raT{HTR1xWyI`C zazEVp*-$?R(P@;TohNc7A&Tx1uAEWNf*M&lCaSybRg56OAPcOEDV8uT+Hk(mkUJDR z9?u}a5pp2obqYn|aul%6B1oDvC?}}Dic)d`&ZE0>FgDd{EYzYZMUj^MAi=03cZU*P zCX>HUEBfLVbf?g{Qi1wN(%c+%(~CANumRjL)at?M;lM>Ao@dZQG7&aQY*b0zG@W(H z@MF8DC;^Yc3(nQh5)x~uhw_}FVh&Sxrs@`-U3b4($8dRJwQrT4#QH{(gvs0RYeM;z z`bmBr)=*YYcGnDi>$ITycF&A5u|i>K;!bh-nd)+b71y+g$-J*kP&+$a7LPZ*09BKR zpp^5-O|J4ZhLfK3V^Txd_Fv$~&)kQ_1i{p_3pRH_ZfhV;(eG~zW*D&VE%6JFCwUw9 z$64gk9p)z{k8GZ~2?qbkhV?D24yi2jfWz~NKp%FDDgr)uI7wD61fmif zzOJ5Iir!G#^&y^qT;0??pEF({7FYr+5?F)~nEDICEjQ?nf-bTTD<*j<5;`LDsNp;qd7N+7S>oPEIDS$p~reW*xc$Fs^K+1O5ZjJ zOPb(CGh>#H!DeCw5c_6DWy907Z>VO;7_NdD9wK=3}Bs z*W0KWn|h!QLW#WQP%gT`v_Ku#^kaXVT`|-KGQw1 zQ2@PtV~r5+1b=c=3Kj_uFg*`ms@D41@cTl5V)u}0UYdpOdY=I8V1% z{kpd1alJp3*`;~9XgVx{ZIt?rP`3aUcKqcQ;e{Ej{E0hiQm(Y^2lRSo1zio)x1OsG zKE3~r)zYW;|9$uU&)>%0E*@``Q1Mk59Y?Xz(rx_6)2~z^0+EX#ETit_`;BTzw1FfN zrlvc4jW>LCBHQgy>AiY&w5L@qXd^$hE-j+cP_mbYCc)9*lQYefCevr3`!cx!&n6w! zT#Be8<#E=EZ+<@4p^OjhMdF&(ShBoeU}8|a3|R_Tv-i0bT&MjM_bfEnQ!;3bq)8+; znP1AqAeI<)l2HqoRtrt*&+XJq=Y3r^$k5`L$a>R=gRiI%c}Fw=KW@Q`8|vIBuqrGK9-kfoypW<&uaCGg`7Yld z&BZB)(4V)la(T{v3w^o=(sHjYPCP{AA>}08XCv0or!nOz?64lW#cZ)Cx$z4QcCri` z;V_{k_z#Y|P0|_Z)!)e7|HFCsw0_tt1Iy;C{(2j8!>2C`(JlzO$3p|hfik(GQvT4K z#!Vo4v9!~NSo6WE?^E_fdR+>I(^?5#qVIyZdq-+*hTB}puT#^E-VnTk(&Co>o$M_w zDa}$m+w)cVjk89g;ef8uD}^lDnYHiX5H8}-szHm~%?X@KrbB9{wSfn;ud|1_xs1wl zy(P@n_i1XjdHDz<%w}~o5aBA&_atNPzr{Hm>pH_M$V(B~kL(nlR+5Y*en!6MKM&PV z^@orMRZNT!S{<{wEqsG6u zW84c#_6jq3?CSK59P-y%&_wz{#rU-YHjsC~_4*wtqo(Pndq06J@rvJRb1}jV49sJW zDMhDQikl8x4t}YACmCraB<9B${QAB&J0rhNmFzou5epa2s%tGtC^{f7Ue3=SS!+J~jvOP+mlVzfbPlR{e*cpR@12 zsnV`YU*TR61bY_B=52_?TxzF{QaIYB)-KM}b<4A4Xl2|?OT0>U;BqBWW7W;_@%Pn! z_E}OLNiz%$^E&@nbyNXLD1yHWymasyyWG@wRRVWVx1`N{(vu;jZJU!8uIFVI9zU&m z1Ay`gl2;b?=|Vk-&{Zd*^&I8$fnOlK81M2x)`t(0HI&S(!4hrlLJ?K+_5K^QEu6OD zDp36Tl6KS>gt(0Li`&Evqs%Ni)R)?5v0`JrZH!7o-qGDY;B4&uOzSD=>3Hl;K;Lf^~Kb;{bSM0$G z`uQ4odHTD@?NA-6BmVtitg4+!^bggpr_%Gmo@i8^&ka2di~c^b!-Se+{-@SNc3IKJtCo^UCneYvLdz* z^YswSZMS!fEu(JPJaR|j;jXJ!^NxcKJ)4*e6G2cnmL1>39hT?e?;Mr)pO%niDO{mAiHM2=aaRT z#)&uu2l9Ffa2MJpoT@>%lf@_EXRwNluC`|4eyb}Y`?A*LN6P(po`wleBOt`Gk2m8= z0jdOp!U#f3YhTF)jgW4}CAi`bQ+pt^@o9wC2k}NJ*3_Z$K@^Xb1!@_4qrxbEQY3^L*rST6kLzd*n5jmi! zf^1!B56D%~N$8&kW~rDs8MI+)VZ<`f(N?_mkJL?&AN75ooV~;T6kqcQRWkg zyXTeZ3QfK5v$&EdX7c7w)@=vMUz*IM-@j19r%pMXH4}5F_QC5i_cJj5%+(#yDJEZF z-)2pfjULaUdLrmwr0YtmAv%fVjlwL6*8t-?lP}1Y{l%-EMw(pFfHVghN1XHkAC#Ru z%4Q1wBb^OW;4k@!ZH-`f&H*Avv4+rksXTmET|nB zJ#XbFZU=iIfDU|-u^G)o2y#z{P==_wP0nqtk`bdSi`UK2H#>P8s4ge~7{aZ=dW+=; zODWp@5OTD5MZSGnk@K)Z#S#O92$LI~)e0rrjXGomBLsPFU2eXB@XIN2m*|Q)<3KOM z&X(Y}_+C=Iaik?|`mO{gNwA%}=)#58Y1D)5%Vt_a;|@jm@Z{-TdQD1t3_@HN08Hr* z@Ih0b!H~&tuaTViJhfu=r9NbdeU?8#2$i9u^^A-xo5eEVX&P0JDG#(Olkprf=IH~g zo5jud;_rDL#F8NF2MPerp7HM1PofBUQ;_5Y%Lmro&5{Ul~ZYh4VI!}nQ&ZBihgh$cb}c`7f6<-I4sC2#U&wUu84 zM|^|HFOwldTG!}bNU_M4Q$EbsYFpioJ~aD%Z6ug=NTW5d5T20Ip@NMM_8W9FWwp1& zW+;}eLd>|y39Z^$W7kfo2|cE~vga{30qDh@ew})SeC=@|9$wlyz4|0|_15uN8?5vj z8QGs6&o`1sBqN0eg*`+myR7V=&pdQL4twHiPJ5}VaQ8InAL-Z2Mu;sZrx-bGV}xL6 zB8Qa0XToc9MsWS~U)8yrw^4E4-Xaz_a3an{?VKGpmS)8Q;)UR}2x(oq{%nE%oRYBMG~|xrT(=n_vw1U

JTcUMzTAuqi9khelDd6a*o!Jhyd3seLg3BP{>NQs0}Y%+C_%Zhx(=kxhtC1#5)bdHbXO1Rr#UpD* zm#j^fLapAiGcb(R{ZY&Wxfk6t8M7z?ZY8o13n*t|KFf;3g}=Eg-&wnbJmkRT*G0of zH0NV|m1N{4EGYIalACd@z0VJBHG_IE#QgStT6uMBs#=3Pf*$<0_m%n*VRLQnzB`=v zaoxX9M|gTa&AlTBhrk@!5(gX|yq4+2SenL24PPl|fK-|F$I~&KP+0K}=wUIn(OK7_-@`ohO&eoFVeYJiFq# z@+P!Y#s8d%^+pXCZTrP}h&=lME1x=>?26N0U#G_3RSNYumC(4veri{x?pV4emL zPxEMS|KjzIH8*A8bW9)KkbiT~pkpY69=V3qZh7K<@)JYyVDxj5Is1qb_!9Azcg`eE z&C7up+MCI>JaI)|L%rXjdYS@$r3r!0yKS2!%0bj5dAqjf(^EDXF7ZGWj6a7WRhKx< z@V*#@-+A8*;*xV_^%F@oCIW-vnFtToPdofN_ai)s#A@0Vq0e~CI=r1Q`2Lvy$Ffw8U9{> z&(#TO4DRqOhj(P513NLqx4wY3{(L;uGYoW=m$+fKI^f#6fS7i~z08^mVF^DIO3KH- zAyy>&@K_h5Yh3#o85KOlMi z*!fRu(@3DBxN5xwn_I%z%|xG^87L|zHc;_x(py(|r-kU@bH&o?=^GUT8wVRico;PN zdAmPrc{`QK^MM`x7R}A9nX%`A>lvc^VH1akd`vs6voM|p8oD^br9p?bsR`yq*%8!g`N30!&2bBCby z-TUf~o>ffRmnYp=OcRj)hbu%sHY1F}qJI269hDT7&{2O+Y>(}0af1a@)oQ^JcA^?! zo%#~^D)L3!L>w6U8A3`81?*#ldA;Y-xAnG1$s-x4{VBaeJqlolVD)tUKdMu+?@3O^ z($mX!$~%X)^TfCinafNJgu(IM8{gjkUD7Oez zWu@L(iY6MWE9A-I_X5lNTFFun2F4)F=5t0=%E3t!a257qOJF;FMa+E1>qgI#vEY45 zAAqfEI;(A_&t(N6`FIADc6BW38+U~+!z$>p%{F9Da*Dvnx~O;6csTV=d#-{F7&NkF#0$WBbY zb|iEdPyAewRd<&jhez1CY^4Ve9b*;w?nRKZnUyV|U`_g=Ns#@*9T35b^AwcQNcM@C zguF|2RN2oCR=E1U-ik`l0urx!U69=O{WhJDV%2+riG^i10z{E)fp|V<)%-p7z&$n7 z?jX!@Q`W8R)QV*PTA{~vsQ*rmTQ-AiD=-pVRtbx~s!z;nXB8E;yWCOk;#Gv!mQJwW zy00Xhck@ihBl|O`gLgK-6s?|Gfpr#pl@uaWLVjq3J&O0=-2K0k3UxCogq+M7@a+^0 zk2Cu)mv~hO_jHd;#i|1oOw{^oDz@bXaGNn?BBFAg$*)>Sr8%*0k~wvJLd}NiL7m%t zRpO^)Cv0w@{dL$F?SuKF^7dL>h8NJb-$!7DWSJ-o zpi(s%XVkn85M^yiw8&0nOg6mljyrDg4U`Lp*l9jUvo8A~{qc1;!0G-IU;9(9u^ucx zMB}TGB~T&|>C|ZF!{jfC=Nv&2yzLMoWDumWeLi%s=0X>?_%t;Pu} z(1bD|X3{V(f`c6+v6{6?tWU)9S1=SD>X6m&U`hH-u1r+Ge=vhPihl;*4lnXo#4v*C z=k-+xIaD3M9@@-NTqMdT$9)$(*sr4W4Upw)@Wg z?YUxLH!++u>poO8(N?^a^jHv`ZgZS6KK2`&njeY^FQK||@ z+-=_BZ3oX}18%7Q7B_}F5(pqo6 z%~3YSYX=J=iT&bLv-khdsaXcIaXl*3Hz>Vn%g zpaL~4zP-{fR;vBrV2^KcKtfATW4fKRB6e2BtkRdEP%>g~yFFQg5^v z&vYbqea=+Q2*KIL3A<`u#{LZ;sX1TgN9z8#;SVX+1(Z50aUxl?P9ux$ooZ9_o?-jV_jv{fPR8<|!kpeG0;O%x}$Iah?% zFx9RE@))aQaZ#AwLcl<9rvhfHbI&X99QdNdBI@C}&x*Vt7Nq?z=-*cHe?$!L^>6tP zYBj;xKv~0NjFXIt!-RAXB_N16h+-^+0MdU!l<92DG`6aE9HEjU4t+n6rD3L-W7DDI zjKHeX+7PqEOnC17HF3jZy>!3BZVV%MaPI>|d0f%Hu>oCW zKo6>Ff2`6`I9_47`5wMZBN>wkC!NNvq=`LdM*V{_cuVLS_+cC?@|1$3EX(;I zCy-pA4&Nv~=e=cV;T8~vwh#80GSDc2E47&5Hx&BR+V)cGAZz80A!t${S)l~NIO z{IgW}GA6sKK0PI?Xk>~OB^9Q+=P42_KT(oz{QAoVGuE_~^)@;j?qtU!()1UnVeTan z$*^uJtdxc{#AkeHPU-nvkh|-X4)4qoW4iY7kPa`IW8Aw@8V&+SV$&@Kt~j}vg*<*s z|4xd)plzh9LTH1T(?mVdkrs56o$+!|F-)=hdIG%ijhhN7Lk7mKQ1FIn-t9 zq#T_fD`nl@?HZD1T)nyTK#)d(ekOmGn$U=b0zFMKOZR)tsw%2UwG|^f;bNyj9{T8tgBg3EaJ@ITo7g66Cl8L`EBYd5vb?E;P&Q(~eWr5*F+2fPg zHc~}j9uU!@;k45kr6qW4*f=G=#!X#rn0P~ok6b#IpFsyjlIN; zX}{Om%|x^(&V;)Htoi%`_=G|jToXeJ`|-dux9ZjE z`;!Nzx#S@>SyN7*et1a({Av`>SfC3UH4}Fk;duDL>VEns8XP;o^D~Yo@{f`siVAPM zpEe>?>ofNUA9?7AGtp~PNer+E6xqT*;9VwhzDJ;Y_1b%;whmuR`Zt1PhBGkpep2pA z@nU<_lA$&LwL49>Cr~(|Cc>cUu#&c#CRDW2nb3?eOh_R`G@xj}ppv%C*HFa-S`ZDC z4vW^TAq@_GO#(7-8?v_AqYy@U%n_Bj*0KQB8I`}6yc@;cliT=SAji@PI=MApOT#1m zOxrB-uCA{SbOSGWC9oTryP$8s^!Qfh2nagh3iqUo2qQsh_HncN4Smwpb+QCV;x?~c zK*!LwmvYW@TLd@M>+R~DRkn(~H7SoaO1Hj7e1U3x*^HzZwgMiwI}-V}oZ_V%Rjb^{ zOdQh7F4U^)bk}odfsp53`qc4>h3pz_t93X)K(+o`VD44e?zEU{|FPLOX&FbDOy%>7 zi{?QPwa4kYyrxQiBNYhC#T3=P))@uEapc!7ATy)#;22n**;t5s-SF>eD>caa=iDvG zIHe68>dwR9BpvOpFq^)4c|}R4Mg<=3&s;619cvsPLISGp(iNvLag$YMWo=uCN1Uq9 zYJbo`p1okQ(!tH0)o2xoQcV4kC(4>80i^h%H8oZ5)H+d;)hW~_zw>{SPx;Pz75HnY zW``+{P_axz!$Cct8Tk$6%!YKc14Tv#>Br#NWb`EO`JUHsFZyjex&=?_v^x_pEfja6 z2BqE?#7UDkEpDuHl5_QFF=%rL+i4I$VBVdjNb zbdP2x7D#gGEowq@x{&HbLAZKfCQynroL=5|k+4Qr#F3D$vW(r6N6Pq%oBa&B(|;hF z*W`^7ac>aN9j@#hHd;{(II-YP&&l3mCmAsG+VaQ8W~g?p^(Tq_TPDJ{ok>JIO#f}O z&(q`5;d0D(+t<=h>EahkOKkK4=`dnGJalWF$Gb%efaPW30!y}Gb11X+zu^NIh=424 zxa$0nYx9ynZ%GsOv_JaZ1>ZYT)n6}VJj(^clw+CLZ~73Q`?Y14j#uH_qXj%s5;DDj za@=WBOLjHaXA)@bDyv1F4XLjgMzNqPWPFk2FSd0Q&2n<6{G_xs2s6X|kl(Gn`iCd1 zyP*D=;yWF!JF$qa+PY^F7wRJPvdwdM7tu^D;7o#{N>BS1eIdNUxyUASbpAsyFZ``o zMp@q5ap6840o-B52pKYq-5JdRs2u}qDBrqLEx>n&g_utA{gpuD$_sJs&}o^p z+Ky0LI2^h=VsHf>;)xUj$4H-A;vf^R7Y^~3d};?yD}x^@>7>a)Uz~OgXOgV=%2ATN zMufe+XfrKoKV&fr4X4fsk5LVf^5w+BWrgfD zAuu?7`;JrfBYlFUGA<>U~xa z!%mpjIEmPuCXS*5YwQBYgHpwR@c_KtxP z9S3Hd$xWssT?}>~8Hzsb&Pk!EeSrN4WzT#uV-AV@9Pdigy!UC&?R~m2Dz_AaHT4pA zv<)qOF9@&FG9bboaK`iZ6nhy(c@=SI5ky5<&pbwV>5a8XMz8ojlN)`*LU6cx13c!P zZ6hgU5-X(HFVgtfDheIDyXU$;4%|^uK%V(od8GqcB zdw=1+l7miTYOfxHKzH@LE-M!srT`y#{(R6f(bX8G$6Hr5lV4g0FT-m&p={EkJ6G>O zJ3528(wu`~kYMY=g8mE0-?Xorm~;p;#(B6Z^orxK2s~${W#$^_PMo@Y}%Uy4l@i?ScAY+hRkqqn1XDtm=lRcO{m zM@mQqSPiLX#rV&&9nv__wniVO$ zClBVpJ_xy>euwPF9Nx9~LN}YZ1N@vcaT#|*L|D;^@mWB3CkpfJ789(_%s*--4Us2j zlT$7Lly0~9Xs2k2mhvnZcQY1OQ1dAyJMtQQ);|?io^>37K&Mly2HHfKU~jT1@Q&~j zysYGpEgqbN6s4z#ln$;$FdwAYh3b22qIDSWitg&_#yp^cnTu){?`V}>*&h?-Y7{ zG5~9C_LYt>3wu+~SV=>PN54KXZpW zMXIf@8tr8cOwcPaeap7a7+XqgC}EX^5std5cEoi{y69Ll8&Kh4W>>mh!XtjbFB^_| z$U4#5`PpZ0*6s5ZlumS#o#E(AniYN9elJcCFC}aMBZfn1;xKzk5Sz(x>6q#Av4%5t z?MPcRy=Bx6Zz%7`gd*2dESzk_)x4er|KHz78b^cr7kV^yUY=2(lAadoBx!~CV>$#J zNSHNaTCmUc9X}QCRt+IC!Zcrf&qQ6)s5$-kS-6ekXp8P>qx8R&4fnY^bKpqfbx|_) zQ2LSN7pDy8*XWI_&owveDmuPW?}mOu(z43WQOM(^zf-tk=2bJ4EH9Y(qLr&qiagm; zJxJUKVTX)2Fai=Zyfa9ALb9*Yg2FmDI{06pk_;rLSl%^;n?Aw0&^Civ;8$G6`Iisk z0O6LhIJa&b%L^B8ZZ(DsxYsa{*^e?|`E-;VL9?m;4k>dsl+k$NbPDx5T*P6enN#0A zpKF{dReJEVUcP~BvAZTH{7JY<$l`nOc87KTI6^tVP^WcNCYhJ6=8hF8`3Ta&Ik-G| zju9jQPp9(a4fg^{gdjfL&rP61R6QKZ(4|?s=t5+@b1`RzQy(JVGaqDjl--!^Z&JeNZqI6!2iAqL|=Oq(zAnpeXy$~-ou zf+z6^+e5GWT}x#C=6g|ax>5{NaH&Wa-Gl|WaIXUaHioW&UoZW>33p1bn%!_Atn17y zA$B7MozRP50V!3N6zxE5hiu0X5=?D!rK(j21((+LBV3dY|DH^5icBL9o(_C81_fOF zj53puq|5e}#$Gl1Qic7>2)agy8z|)pv>Q%6Vz-vE!??LneQE2Wj;6hnL&f~vK1l_z zQk16kkQfGL9L2Dd$ykm9Dbbp#AhEF{2_Rczu*hxW#;8#*{qsJ+SQ+k~>9i1sWjF4CT%J#Bt1zmQHz>9QB-D2?nu<)e~QaFIV z^;hP}dSTShP1gmPKQ{Y4mjUk%A#g)Mc%TUCOMA7pzql%ZtG3!2Q|CidpPFn?ftVd4 z>Z65oq>#|J-&|$6RcIthr9H-I>H;9omd~zo)H3tys)ST+c--I>yo`6RX;!o9fn^8q;B0vn(*n?Z$c_Y%0 zfYwhLnU8!Jf|&A2jF3v?WJakl1Hot%>~`QaBQ^3MfVU8fbpjE+XZgCw7jJ%^#A9iW zlZ_ztzFdnVhM%sGOq!N$-I}#f!Tz=fl^oTVK9*ONZ}js}Z}IbA>v|j13^>T}r3KL_ zk%^Bs_JeS(SSSW2<9aZ?SeGu^1(Dm7>Id+XY&>`7`Dv_+y)o#}@_oxmEqst)(5`DV z{~A=cXSA#z&B7u83vwgrx-)F1r2^$i+oW5x(`WQ+=gp(|&_$(myYi(++ieg7hmO*WEH9X8bZ{t3;A=r;myUDSMAtaQ2GGLJY_2*R!MrPHzd| z=3QqBkz5!Fr1Thv;ml$ax%1p{5RfC=v(ivc19jfs9MVfyZPSS!O=hLinxsv2fA`P2 zJDdA}etEW+bL%+@2LH%?HE>Ih3!(!#kCz3B!^2?XmU5?K^3b-M>&Ax;HXh5!ai>KI zgmPUxf^ig9Uzl#S7XMxiFTFQpC(AL}8lhqJM$D%t>I1mQ+v)oa)?l#E`BrKuFT^Jz z!4QhO%06@)UC&siI0-(a;|f;Q0z@$_uh-eoVb@oXG4Ki({Jq{eon4ZrF_H2 zAmec^Gt0PKd3PSNAjiU#KebZjHnV|BE>=44iw(b7)CzF&0=d?Lo4y7PF@Kr?`CLj8 z=q^w(RgEaW!HsqmSHcR$OCkzO%92K9kQJbkJo}C2=FK)>%vY3{Yk2FQC=miRnd_QK zTCqa(A>=a+#q``nzQmFhh+#UiBgQTzlb_eCqcl;?;aF4bE7&C+fM+_TZv8$1Hqub z0wzRufLy1Bf2~uvvdDRS{t4Qkqn2%`QG-K}%fz?QqmttF3DY8-x8CU>Z(($V?kq5;c(mkhdYf!oHk8ikz zht7IE@)VC!w23TQH41NU0T1D$Bhaso^mKSI_^94ph=>`FQhq1UmGGB}zkcz)EW(|p zmf1?u|6Qckh1+A?Z%MxP!c=)7Rl`T`I2s)W2&F!n+!5s8!ihaP< z%RFelTLv%fr2t|+j~#F38BNFZDsAU;j_;{QHd&+7{W3Lrxe}> zP6$W0?2aJN@||QkwUx~j){NQK{hGc^q1JO#`hvVP+)VpIPB_A<9WG zZ^cd1r=r$gsAw0NU#WgbZxMPhX+*T^T8Qo5Lo6tn1wHk0G6^;~PRP^{lo87Rdko9Gl zXR1C2K6PM*ppX`7Si7Mo{Tzih$QynN}39XS;ue_P^p3gKPQ zc`a%*k0kADZSA6rdm|IG$11n-o%AFJfL_21bnk0?*=x>k@LlBlz_ z67qHI(*BTWbn)!9u;0yTf7S-CO%m8nZTHzLD8<6?9UYr$edQhzN`I!r4_z1B);=vf zd8XN3B$=*8&c5v8p}E$i&EPy*52c9C4vHv+cgoB&kU}t^&U%AIf1k{w_9{(T) z#L)QTr(M;}bYmn-J9B26UVUQ|gwaPKQ+cMi`Aop5q-<(>R_0@XfN^LQc^zt(wWt0= z6m+G$#cgRr@CH;pI5oFY0hR-SPpEb#;Fe9Fiw->&_Tjds7RMK}=n7Ep6bn*Cj(zCh zfR6<8{Ac1OX^*QytA~Ga@n#=6bMoNU%9 zlOwQUmP!Lp1-Xph=gJiMn|{1t2>K4&ZV#b?*or!RlSG+c2_woG|}vEbQ%GVK23x^t_i-;NFC zZoc{j2*xx%F0x7WSdSOG<1E58M@;HC8$5^@ey~}v= zZN)W^N2o(m@nJ@4D!=MNIT&ZT1{dEZbw4;P$M)S|g#-P}C46)Crr>0v^cViv>`iBl zOW)E$kJbuFN=T155nHf#!L)P}w@j*jhhJ0&u^g4BJD=M!0*UOH2TK=_UGXt+WbMXF ze&w4OC$YBolMcQJ!E7TCVRzoQ9W-DGiPlQHSrOs2wgU<&KKDcn3sbKFiGhIi3~|P5 zYcSU68!tgCIy?ma>RO@ezBcWwjlY$1ho6=ldhk)kVY^KMEXirCjtPq4X5PdVJy{bE zNTbQWT;xouEYWw{Bv@wK-(`Gj9l0SWe!RC5&YlAt`a>SMekUZurx+iZ$ctfj!3Qia z9g$P{O5|YNn$Zx2;~+UmM#KdF{FKJ=7M?mFgPpmJ(<7e8O^xKof|Lqz*nZ{EaWwb~ zHxgE6O4#mw7euib7r%gnjW7YB+h%N)mP5kNG`p3hms4KavGN4FXmM*3?DBb) zKb?X5a=)(8em|jZk!ih22#B+}&JO*YvcuE2CNY5*o2Df%csWx< zFt43EUp!-k`lVb5vHYC9!ilJ*b1aS}DEa4J+p9{L>?sbfo$H}Uw(41PKv(*Ri~YDe zLYKzq4v&c;?{e{@*FbuiZM0Z1dr8B`8B4^XjA)_#+7?>eoc;1VH1EgDNlTA<7$K^s z_xwIazwS4A1yH5|lt+l17x}=t39}-ZuzHc5#H+4TNUE*6eaC!Mh%V7XXW*f?g&h^;ZmV&PQ+6YdYF`@e5|%xq2_<7GIj;xSZ1 zrnORj1Z;(0qhip?-kh4QI0CB7I-Bup;bxjs*39DB6r9k6=Nvlg<%y0tx#juvprS|F zQCKc$Rnhp0M_DHtT8#Kh!`X=_B1=FbDmaQ)@+!(Hi`h|1{ z{7~eqBbFOoK{LA8f9u0_1N=ysEMy z_b>tjdm+ccFjgSHoLYP-Mpau^0>+aUfz_;N7aT8_o)b6fMV?&V^=lRa00ESIQt)zs z(?F;ehXwlzpRAcX&L%)yGlm(rx= z%eS4=jsy@hg2geLfpJU7$HgYYR{9H6<|&xI4tAyxxbmct6BK6}*5v?Pb@T9Eg=o00 zDmb%aJB7Q8b0NiJ-$2KxCc2EDa0Nk`;>F_V7MDRAe0is_L=G<(&HJ2&35@|{nYPIr?nr`fBz$Yt<83?H<9CEoYaF{yaS`b zqjo!|(84=oFeC3=q+Y;I=*rNd;=5lw(E8UFuV>E$f2I*&&gb--{qRqAN{whOzy>~7S1Sc58xXr^p@*(+K{ zKMK;1;nTv~77OD}R8V7?D7N0W1iYtbczbWG2&IH3HT5*xzEArqX$O$JRx7VbDfC4% zK`~o(*`{>SIv%iNm&1ePQ&UA5gLHnAw z$3OeI8KjAu%#N&MdR9I*Zb=o(hQRT7uiP!P4|%lymE7^SLDmb%(DJe-dm)Nz$g)T? zK^!&1FaOu~1IIICaHL>xc+Kk_p#|JOxhhw@n%2MwG{lVp1e<8LmkvZQZ?C^{;(7U% z%kB}`U`lV{YlP{}3tp(_atw4L90~w5fvR1fj}$>bFL_Oo9#c=CSkeNsi?*8N%|o`5 z#{w(E^h8g^nCnwVWtWSpBDLu=r!CkWBB8dMzk6&_SsIYHICd?32?ptnF(y-F%~0!1 z2JwnNx4)lLB$H6iLW=ght4}0O@6cd#rx%)lA|EsORDvapIg)w^K;>*)HRL0*IaNzv zsTZ4Nrb5e)>3p1z4I_ljEm9_`6-gUA@vd4JVIPpfiDv8uq3)2E&actTb`t}$)8D?C zSeMf9@Z?TC+3KLbaqkdhv#v3-e|rdtNxk@L@banDAIKv?Sr?i-*ThlG%agY!5iw?b zH(Tb2*qmNf#tuO&%c?Gl%HDPxj?GWQ)-r9TlB-J?SXVSZ4%N$9MpP3WcM_}AAw3Q| z31G4njql#L7$GW=VF%%k%zLymKLr6}(eZ)`lWP4r$&UeK14Pv>2|6P+nR9 zb$I@4SP5cVrs`93o>BYIG68`*E8dHH-j+Q}X>{v1@Us@?e@=GJq~Fe@BxpFd1-?pk zn{GmI8We6$%$k8=7Q9xTPjyoQ8gntAYvc0CSqahubHBp3mji%n9bOob+M*JXs-V+X z7@=bDzupG@q2Xpw7ZbozyNq)Je4U|m=Ocx1wpt+uZ(QeJSQ_jZTffT@YRZ$ygxxVAaA0^_I7XT6klg?xy^@lH z83x=S6uDUbtUCOB2G{Tf^t0DcbXb`eXL|sFjFz{VbtTZH={h9??UWE8TlO`;AXG9Q zVb2)Bw48+o9%WG zP>$$Oun2lzpdpA*m1jc5{Dg0sDKVO@vGd%W68`$vO^jIG#i44NjXo}MJZMduhp0xJ zWBOivycj^P-Ig-SQ7u)6vetgQlB5{YLndn+n*Hr9|4I0zBnb#TEB`m|j_LR$drbZ8 zFRkG3_QR=En>f{^@ymtR+|`DHeORtoJPCh#{!^-yYX8aN&p^6hxjspDFHL$=LZtKefxQulGgc+ebY z;)gt@+o6AoLtbqmS~htTi9Tx;a+T{w=z(sQw5F}Wj{?lS(yLl-c-m9`Rw~qbfF73X)z_W`)Lv12TaBXrFMCyK};Q+jPxqjM)C& zp3lVX6piCE5=}>@VW5bPo~4ddhOzk8!l}%A_rHG0=ao4C7T?LPm++V>fY;t~y?L1o zP{v*Ro2@g{1$gkc6}HOPpxC{V{0+I?XO)W;XMtYi#)ffBJM;M5N|HB~gTrS)?JWyt znyBm9%!@$}6Z>6Psr43--zf!t4f>BCsm4ODg<^{(mdq)>my%h@)@Z2q!*JcEwmehU z-8(s2#ZMoU$I=SE>u^rv>iWD?_i(Ts4oySh8|5V># zH6y$l(tZXup`rg@(^(XGRN<%R%!yK;ADXr+gb0=cT~gUi+K;hB+lJI6`SGkG?7|aN zP*)xyX~Geur&BTtXAv_|=l9LTHQ#<>rQEHDGT%&^TE4Q_%mI8Fa5V>)F2v_dhxF0I zysw;s)#x^2ygJV{ebT;1wQ_{IQmqf*sVICsy-<*F! zO`l|ugG$A4hCMt9?sm-?o|9o>4#34Mc+{Dnm^<5agPGNkR z{xI(Y&JS{#iLjfN85^VL_ zX|z}VDH+z79TRT&Vo@nmAEV=(VU_us^HEuQKOixgN0{)SeI+zgH%V>Nm27aHw9oDM z()IvDF~K~g7(uLEk>rzVy0@sXa>vNgIR?a@Cu{~wG6neOP(DLqN5&pw<&p0srzH)o ziq7?TOvfNNwHRvZGa|Rfo~~FpObc)=*->!d`OCfbpnqev^N`7IRTxL5Bnwj=%$vs02ih z;XlY#7w&;vIo7aeV3%V_;cQif(8gR(G-7}<)4Zst*6`E9M-5#LvS=5LlbS4t17qa% z1)m+;tAB-kGT~(omzV6bd@eb`t2=$@2A*@TN~83;h~-ej z58L0lU`_%DU83z;Jj-1yp&_H`13Pgx%=oIOcavQ}tMmYX-7kJ3Kn#3SI|^Oc(bL0pPPKZ)Tv-3UnDBfUZc_!EpP9?jjrCx4)8e zrA2n-7#t3&CA@gb_uGBRruRcWsE{su_(w~MB5O}x&Koa6z!So(^?605tNJny3tja5 z8$|SReLR>rGmgleXT~Stq3@28ey7GFm?>_9|K`a~1#bQS1v&Z^F>g!)`S$>JFS$%fOZOMm4+-RpAi!+;p*fZy6duNAG(-mgE7=}aaM3Ql=mSYP zHbp@Z(2)g5g&a7c;FeOn#Bs$P=~C=&J$eF;lQTp+nZTQ@-(+IRMI$x&?PFc3f-46F zOD>;4vSZZ#jX)GYZWhXVJ)I^yQtaSx=kejc{z{qFZrFUyWAWH#|9fnHAC#V#yPc(X zD4SH{#^`J>b$AO*o&E22~J4-BWWMFn^$!u@Q&#x@7@yb7{Y zJiQ8I2d0mO9XOrg6v*iDeGp+NQ<{x_Kv)NudZK;16S}0=ne3WK=wcDlw>0uDm9ZmMbid^lL0pEx!TF9V!^XG!-Vt)>>8A=T%EiDyF$+ zL7)XBgA~>>)}&Ta9}2EVxgbMIyn)UH#Ds;k*cg?uqf;g{&2tYDrD{rRX;6>2h42He z06QL4ULdffbNN~hdN$x?(O>Sn`fTQ3>%;L}Q78R0S0nozX+IF4p2~+3cRX(pURwg` zvD_>a3vP&eax(j)rjBF<>W*r>&K737(Y#3>j&?v{ZJvTcxFLemG937 z1!cOAjGCkMkXRqv%WqOXJq5LBCezdHu9p*mWC)-yOv7;3EQE(8-r1(#%i_}O4qG6L zmxJ!_wHTM`^y|0^zlZX$e3pOQcD{d|w2au%Z=iTIq;h)q$L4_0s4r?#_@<{+u-MBm z(`tblhH>bLDsU&Ih(%#SZ1xGLSSw=0Z0oxRqMDO&k}Q}6T9N>dXO92s4-M}xLS#ZO zKnG139^d4oEH{CIGxq{XWTMKiT;3**Mn|E+#;7jAM&P8+F(oj!ws^cL?Z!mq?~gh; z0_4K%O3mZMlcD3we!@Y8ow*O5Rpp9teymJ1(v<^s@_{bwNsEAk2sxOa$?O6TaNTIC z*2@Vn;T;@S)pHVwG2)*T){*?2`~=&Y0+^ZC&Ghm9`U^F<`Txo<@}#6Wj`l=NoAcdhf6ikrBdTVB58 zQJ+`X!Kc6r4I4YL{pD{rl9;ExOKBJU6rLkt{VpmkFiDwZN(;vTVNv7yAKHE_F6YC? z<}?jK#htw&g7#Ob&g|b#Q$glTe_AQNQ*%MjLfjE&SRhf;zHc< zJ8D-PG_L38<6WgP>z~?F-?(1Vl?dtwky@3%S;ab52$sQwI17Ro(DY5W4;{p|xlYm? z;*RQvu=M`3N2r0MFLH`?-LVzxPm!RW4*3dF_2>VG8y-Edg6SQ`8Ki@;bgJ z3N1?3;wayjLG&siTH5eFlXuJRLObp*ryWuFl&xrCHp^YERiy8Wm*>6@@{)Or0&EQO z*RaLcN25GOB`I7Sh-#E3fz9ofjOD-i+uZd$ig?Nhqkj-pflCGX=!9}x14nZRtL;>U}ES1aonhnmNY4b*)(()>i zYH9U#J{Hg8R=x~^_;`{@gWJ#J*brH*>K~G7rQ$G!&VswOpauWF-`{uLZj1$KPwtf6 zf|4-P16aXb7{aS=uZ3xwOD(J`Dv%7P$_^^ixhb9fqtIOMD5J#*Q*w0Du0x{{1Nt0A z&6NRF;(>!m*HGHjW{~YMKKI$NO4T@2+ZkjHAkRXihGvY8vU+NE%wiagjuAH7O+H!pzcJhaj+@WMO>we1Yj(3}_-1C&Y=^jgfNNwAz$3Hz zr=kw>k&Rx&kqm<@b~$=Kw-Ypny+p_vqwI=4#kFCF@(ZNn8M1R_T#&8#hNPlcq|}ja zQ3CaXbRM?wz*n+E$iA+3%dr4?(eqI~3THf}3I@76yBka&e*Y9g!Vea`Of6GxS9 zAn;@O*R8IO8j`g;T=^#jwoKNs@&WSQlo1fwBI6aoI*>JF(o(x&>{nucDcvERM0DDN zge=o>*u5%bilxe`i)}o<@h}i>OegAxpEM^K<5Opf=jJ5JRVn7la6U2Wcx;UC zm;z2fF@oHO`~PK$hi!zc-@;Bf>qmUdA{oQOW5IFQi4{iij{7aS{ZfzSND{v!_ysF~ zRonoOjBVfwFQSHyY6rf$qv&xJ^?z*7>g49ziQ%b``H#z!fj%gxC;}}aa@gO^&FS8J zipgHK1v({@sK?F@P|bh;DIMG8@r_^E8oAYQMNJ%B)|bnt91^uY4Xh2WKi){kxOfDd zG6-vMU_B&x$uY5Ll96ND!znezy@3kVL-U+B5C5BEFRMXVR;V6K2=EZW|D*~{&*p6i zioB$$Hf13lkrmGaQr{9lTMcy@crAVi*O(o&wH62c!h{nTPW31-+_UtidO#E!fzlVD zJMSLe*g79c+`TIO-jWx^TtxfCj10WOiq`WD`&MK?10&4VFk>ICu@wKntrn2)y=9yJ)ktvDA`P z=Gi>A{s+@+a70j?b_O{41_(kYQxu-4K=j*#F)g#zG!yBG#x0JnZWtz|#-WCQ`CPqvl2X4mgL$q>+ghI8;& zKAZTPy1!T&tGxS{nu1tJSTv$}(@Z3TD#ct5t(Y5DsTAxSnY%(pSAj&w*xY6e<#gkGC5BL{sg;|wQru^L=OU4%j1jB{C3zR4 z+gGJ1S_8fExXi~TN4-8~est)ETVsMql;{I}s03ZE(ur!Es#@)ZEnnnUDSv^E%IvrY z@e?MJCDyj$V{0U^&ei*m4zJ>h-uXGE(Xr2iw(_~5VB@l=X4lfBo^jl!4t zG%dXyI1UnnH-|&l?q(;$SH}P;X}A&0IW4EkWqQaVD`FFgEo)_D85S9&g=6`NGJFe` z8GT(aGu(aMN=^kHKtdoY{z`bE*&$8K`Y8f>5uCwjsE`^b74s92xgn+F;6-p*c+dL0 zv*+&-3SA-kjX8m9p$apBV(-m`qTZAwlfZIWKZ>@IaxP^{f7l1L;A2{NZR{W$cG|>s zO1Yr?`)!Lj*;)~`nx+{YO~fwPld87}4RLBkEmh3y?sQqa&IZGT(1kN8bAC+LOr&sF zE!;d@c1L09kkD1iU$#ON=lPn?3&sKNa;`^IWq zE3d4sJaXT8=*e*$hqDrA@QPy+y8iFOw)1OTSnK#X^eqF4Xv-pj}FtM{B^_jDZYzuxb<4Y2aD<$JDXn>j29D@QV|C97zhYFL?MqvS713Q4&3$u_&q)$Y$BG}1a<#-l+4BjA*{WTas zUrcGU8(R{wTW77(XiJ#?b?_Jz#@@Meimbm7|;l>0QECq^|+(MQbT84;@z66?L;hS-u z)E9^LafxBcHj8W#y@3~$9IXP`FG4b(2eMP1lyF;NLuz@`xV&r;Dk$v4*Rq`J4P`r8 z#F5eLoVTZUtFGn0AG&6*kJ|Ar5Xv`B?S16(?@6*+Oc!%V()cUa=Of%GSQuP`N*?0C z@JqDVn0Al{Its|44DSU*Sfv`*lV&2`-Wix6Y!2&GQNVslnOu|d6Q+d@ z9*2-m*6++#1f_3v=`gQl(}mE2vbyH-F6uvswy%5w(=jCFOYtEPGMjJZmEJm$h_O4MeEQ}RulnJ-^y5yC*3MFsa*KvE8`be9pZ;7V;q7|)_>q;*g~so*)!knn&WER z9suU#_f$Es>=eBcCz~a~;`E`qujMh?lQ0*dBbSBiwQ!&%P#pLyB=-dUV(qDU5#`T(%Wl?>>V^?V8G&X13;~?VRjhHt{3@a@Qzk}3RLE@XY{zP{6gR%;t z=Hq*_ADHnpkd;xP+NBQnt#}neLDZ;$7dr+C(_32G^{&t;zL2rK{0eZyf>A?YAoLD= zg`d)ACovfS433v0QA#9r* zzw-K0T8wPr@`r}1hQxmX*v;PGMOKNs1-?4zM+W|-zN>Om6@wKwpWk=ws*7DHc|5Ns zFU-UzBioXdL_Reo*6C;|B?)Eggps0u+zJSGyZ$;<#;g!2u^!JZ!0881>HSH97m){DMHlP{7P%%n$ zM;4vcJ_MX<8YHr~OzUx@Fbuj+%8q#%`?HDOpyT04s=EqE+_Sj)f9?4&K+)o=!G)amruR;?yIDTp{yLlfku z&gUV&fj0JBQ*)+HO==j46u!5+py~f@$6xb!{F)zKSoh#RS1hi#6+GY1O&+jx+&6Z( zGL-4DS`ztR4FZC~U6m z(~|l&TVM5~cxmwLPx4sPn{ihe4eDnx9DJ@qU2+!Ny*7g@qJIwkz47>zv(tH(WAnTX z+r8s>69i#agAxo-cbON27++YJJeKA1+&lzAS`D>IziYz@L=$Nu6A`2}2+$Uu;mcDF zmdRo(?8LGcK8L~QmUD&22$M^0WHG^B^dT!qW6wsl5JB+&jv;VmGO(WqA-&5?og+$M_ zANGw@tGTVPQ!d@3QyqMH8kVb6QL~sNbb4tKNt;+X6gIO#KPug}vu>Goo;xJxC%fUU z!Ds%@acmpt>F9xRdRG?x#7$~g@fmdg>MgBA5Y=Ljk#0VSm5C`X=QnkisjT;0mC%LC(k z!;vvBBBNL?_MSc$bltz$!8~0D^(eT@X4b@L-H})XT3V+M-S|@evxo`N(#D_Ut#FCM zuC{2W5Ue*|6aX>u7QThkb{w{`A?iYeg-w9Ib8tCpbuNZtWzR%R1j8-yFjW8km&S?a z*z{-ynhKpfE1DKN2589Q`t%b_rE=6Cs3qmi+!TY3tCK*abO&NcYC=aVr4-UQ9fwo# znG1m(ljDenUaIK4{)|7*Qh0x1_rK**qwwGIp>Uqt&-6mdeAD#%A_=0+!};0OmX{}) zEq2)oQqL3;Dj6L+K0uPo%i?1mq2Uftu*^p~tmA$uBRKmdwYQ{4B~K17PMOA86m(gt zlwB`bVliznP=2o2<=z!7-Ix0%m<0J7iJDEv0Mh*vy}kJP zbQEP6s`Moh#tGg2%F=s>@K&h2&_*T<2;I$|#Da7d&_1dH$%c+4d;(@n^lKUCkj6_n zbR*+Nu{h-loXaDYuG{r@4i=sxakz+slGYIvz_CfFA_5DAXH_AuFfsCb?i1gHi+tk@ zZzI@MIc75G4$I!^n>{$0&Laa-oq&pMm9w8$iP^P9E(?)nPx(>Y72~EMmI^YOeQ8TF zO|W4~(#pVop~o@R>s2*&Z<+^ilR70IB2-xEOb6-k3OnPU>Ah`LU_ueRlA_O*}ZvNn9N8jUvewBup0VC(e`3wH_LV8 z=;TM9W^Lw+Q_uUwu@3jA)s{nh5GoqG`O}-zx;ll$phT}&+P(NVz+Co2akT4%#cBH5 zAu3qw#TEv@D;tWu%pEnj^MShc%IZk=2;|^D3N5+O?ig%lcda60gfR(K7pbssljMnz6R1vVbJTSL(XD2!yLrQKpBXQrw)+5^akfna*R&pz9I zaFlH!9+WT+v=J=qcI}}laV8i}A-{ewi+LpYen^<)e^LMUaV7C9BE@AG7A7zAzV>jdbtXwDQ%1^Ki7>+J*-+0LsyRPpHjQ zfJp2lij^qIbZ9sj#a7itqlaL**Y`_OO;G}QK>JM7tItQRA>(U=u|R5pf{KJ55r^Vn z&(-d#Q%4zICXB*Xt%`G?N!5fNNru~XdLk4p$Lo~0%r?eR`NyPu#?38n;#LyVO8dAn zkh(ndt(Cxqw~$cv0turNRwHEAc7bz_(Bh=WScm=KK)CDaKNS_9=JfF4QL!zOldgSk z(N8(y{COEnd9+H|7otU0ma-)zY~9QlOz$HphkvC|tiahz_TY( zm;(4xR36IQEPwVl@|#YP0leovM|JL?2h7Z#d2vw}BgrKQL`ogEiS3lIOa#4gei*p| zzIY(CEIDMV7A?y?Xe$HcoGnVf;_E)~;UM@p4w~sTd(~V%UQQ&ee2X;3dzU~xadtO9 zKSWZHhj$v@tq+*IlfV3DuoBun{5F{WAQ!+=L6X|g5SFK!!Q~6T-8L2*pPhv%Rq=BdJb=HHa2tLdyFB?NO4`gI4h?1UzzGzwDi$a`B%j%_bfu+N@GbSn>o^^C4b`)*cs_!0vfaRb z4cr1ra+juqU$ykb*`)@2?%w-_TX8^5Jy>Of4ehQe8CH2IjQ`aA9L)Vus&Y+UmB#Z2&KT~E>DQL| zzWv;aTF}P3^QHzuRCW?x*KZT0$kVool_hyC zl^QO499k+E{eDizGE<)p+y)XmgIwX=QrZ;~b%Q4M)C)j3kQl0Gk!`(2qEhNntRPah z3LCI)w!<*YR#we_x^hV;Kka{O(_I`{jhLiF_tpyF(knRq&Rm$qF#Jyi2w477sAM2L zLy*QsDvx~HQt>C07Zsoat0ReLd4INDMzaO_vyG#H^z`3AZCkfBM=}r6)wdEW2AS`$ zqctR45=qJhnUKkptks>m>u(vAV2)rTgOhUR3Tq;Bx}79@9iK^R!*rD8Q*@UU#z^UK z?kH2sE3HN43n8_0Q5&;vd$I|qr$JY@Ss(*X(LW>d+U7U3@ZuRsIjx;(O?T;v`IRuC zix7$6B1!EUV84|l2bu(6%wR3pi0}(BnpWPs>dO<(tg!vnQH^;S>j#!@2$aN;5;kF@ zmR0clo02f9D!zP*U3qOAHkXe^Q1&QvSMm6^Iz5lW$jmK8E3T7Hm~cM0F7pfN1H(42l|j*cAOveso}pwCTcPz)DUh-9%(9)+JPiO?FbS zLiy%9qJ)&dSl@tGj$ab1>?&?$Tu>kVH>VZbrDsFbz~KNYpY!wDkrB3kJ&e3<*wo@t z$^kE5%T^wYm)X4%gh9I)UJ!5+HzY4gHC-hQ73BlzSFSBO$9}xrnlZ%`T@1M*@)`Z8 z4hTX~5z5LqF?D=3Bv?H_R$~PEc5DtP;LfXuMTk6BZ&R=!$a!fKS2lBzVEV<+08RRd zo4gfpQ(OycPqS8z_m>9U*o9iU)4r0BUp$NSy~5p~Z2H(47;OFuKi_jP5-7 z0aTzW?Fpu$<8w##?_Oy^P4$XfB@{Pe$@_c1MfWv%{AvFbDCW2UZt7^SPBY`iATwbQgbOs$QO-_|!w`C99)&dRu{f@e~wnB1p8ORhOLb#2>e3?uMBj3e}q zvQH}~e2iU9w;GviO!t&FRFg9wJpxh{zb$#i#-|(#nGxeld7c65@LRd4W z9&T1Iq7Uww{qm=3b==i$#*r(P2SoU>ErC|l;!##NV$idZji=j$D_l$svNs+Wx)6&) z>L%sqLywxZoP`|=>ii|qZtypj@0y1-FWly(uvY4GX%!@npAUy}&6F()xBJ6)6abMh zx&HSXD?t}HFLu{!u~o)N7A_yGE_nTB;9(V-yU<$l6-L&k4+%Q7X0I;z?~8^NN)!Ii zn`<4b0hO#T-g!pxFgwcoz=0d=dv8KKaa|T`th&b6FS#{4gpo^NvI`#91CybcZG*+b zZ6&z-Lg*~>ScRucg!~c?ZMKb8ids3XGgJ#l5@itd@l8~(CY7@-l}0&602(}eR^8xj z3zz!;yR@Uzp7106x4NbcmC0n_DT^<)2gQ@|8&#+=-0mNgv_3XdfusAbOT^k%eD*${ zBHqiukKWGJHUDR^XGp4n=l!d_la-Bw`m3@{LdoI&R(ev_D1jVEwmERppNf2aV~{@c zedmfTGKRWun(W#Led!G?z1WUnd)>2x2vMbY&6!obYy$oHh`Rc$a+>dahNfUt{5uCDjOsID|Lkdu}bH3Y3 zF5>U#oXbVA?SrWgE~b+2TQ&byDaazwL!KDek3y?MOb1ZowgzOLWB31^sr6G(UfdxA zt6tWD`5@#qt2{-1-N(1iz>mXQ%XR_pm!<2l!E)hny_cE&hw9lK<&MkE;{`{av#S-$ z#K^L4CbT5+&caZZKRn_j4OCEZZcQ%-KoqqzwN>!*J$x&TT( zYLPd$ezd85adqqE=&e3lL7xW>d8Y-ULbNE%qf_QYER@u`TooPYp%+-fa>u8+9h0@* z@h;fJ)ms>5wZy(Pi}#3z@|@pg=%)o`;YU_#nZLp)ni(cnogYmy9*$JTC^+%wok389 zE%UO`Uod8ydYg5_@CqKOqo~lFs*Y~IXS{kd?GkkMl;>P{D&H7*MOrbuJN&&&mt>3j zdn$P!uazMZVI}7a{8V-+7mZoee^Yph|@eU8n|&b~EkWIcXMfKPX590+m%nA7<*K(9Y0n^TcDI?8I8E>AOc* znWHk>`o$3?i?C=5UJUNr1IweTfC^eSZD0SDQ)@-b$J-m*3;t{8Y&>S>FE*J?P$-W# z(b9w=QgeHrbo(88!6~$72(2o%C+ik%z+{&u#lE-i!=HTCB@p=w;XKAHW4}Z$pd#d zfKo*=R#NipDEbXqg6r{SH3==#XgAeLpOhTp+=-2i(QEl5J5FVT)$^MjEF6gX#dUDJ z*Fl>STW#foKaw5v@eijJ-i}ONN3zwdKofH3E0rK$le8rAB-pAi@CgNw;Q8UNUNluA zKk+85;&gUU0uJh!FGG4iNe`33FWuZNwBbXzLZ%K;C*xCwLTs$upK3>`|eN;9-#JmKArE zUXIi$0D9HEIq`PP_gWI>phib8Vbd>W>&x?Q^YBp2^!qz#?q1{}F$>K}r1^eq@?DNE zK%eMe6$+9^?XN|pwJek@Jv%M#^p5AR>q+JPDz3z00Z)wG=d!qfm{v|uI>p+2b?Z$h zKtQ9S%|R#e`$3M2c1~Y>vbRnW*Si>$%{i`Yd!JS+u3D0}-gm4*s#5<;|DmGA;?)_+ zr6zCq-vN>Wx*?!9whPK9ksCBdy0_l5sC6-<`g1$JwEd)Ad-+Qc&{0r0!3Az63dAkc zU2OUeL~5tNdq68L|2KEhmg;Z_-Po!={JH6yq7u$+qRW*r$$`PhuKA5w$}WO%i+L1 zqjd3X1eT}!eN!5f2$8Na90ycI-C6E#g~#xdMq&wiY@Xkaa|5gP9~#@Rsm2u3Ioijp zWy;!$@^NswupCdt^jO|9|2<8+=FI-S4-dQGa+V0G*y0InC{%|*&<&;ezbacqaR_+o zXXOm^GbSjMfd82pE%@60N|gwnY%%Poj&$hnR63U6x%=_v!IoT^NA zs5nhM5{}xNLd_=RpAAYu z);HEgu}v0eSEUVtOt7med!X<_4*c!i7pe?&@yR#?wPOyuxWb6>kg)@#5(8paF#CJ( zF`P8?yP$p7PKK+B73Cr4AU9CL1&bv)*mQ1MC7$EBG7^lmmaclkwKn4ES=z?Jk+liy zPN9fKG9@1{)6}MV1zOXw8EtW$@GgIh+V?*q_ol#lm5)R2XbD6J158jR-xrZ3GjPz$ z_62t*_n|vChlmAKoqNU!C)Cz*fdFRxd1$s}xB1nO{uuY`5c!;8Z9|GwnV`Sx^0al? zWO~&_zh-;qZ_RLO*Z;0}A(F*T8f5=W`W55`q05lG?KCZ4u~3uo?y8k{WCy&T+x}9K zN!Y{lBT@s40Iv6K3K~>sT1ki5`#+YWbE~)M!jqz>d0GtQApIwQAJ5IoAmS?sH~yK= zps;a46zvQNoTX2FAu6#PRJ<7Hiy}9HS5nxY*B8b#h1d1|7ak{L;XW$b*xEh(m~eUm z0c$hwEE}9q+AQad%`APL+(8ECudia^(5*el&n-2r^I{fPlmG_pXYuMz4AEoKRXNKs z*^YRLqC^3QBuk=P4fXrlNIcb+N>36%zGM`FED;}_KL%PHEY7l*X!VxNk1bck5hhn~$MS((COW>3;PQ` zran!OaCpqaYS>zaChCwJKV9|P)7*qFbCF82(J<$_eti$hPUWH-S@efp*Qon=G(~DxF7%Ko zFXk3)z+zwYaRd8a{$qsK*oVZR_^7KL>?v9rsxB(G$Ad{HQMR;{k(Cr5bp@ry^idf@ z6)ib+DhyYeEUP|=y53dOW^{k>76c(h5%dS8-;ZH$dV|U7R=hQRg_8Ow z{NJOPcfRVL=^}B(5A-iPaL1&dZj)$0MLRh>@T%87Cv0I_@YpPzT%svuSu1jI*`RP< zP>H>HTCaW*0-2JR6d?w)U7|KXE^Da);a*DpEskU)muP^)nlX~od@3-mOuK|#5W(Ch z_MGm@{>iS?;)ngLXtfL!ekazY7nndg1fl$h*s* zuQY8()5LS0cyr2Kv&uxzz8#w1qm3%YB`heJB2mXsRE3n2p@lfa9p>)#e!wt#5;HnT zqzoeT(a<$NHluX2DBF+XTlmB?T*N$Fc1j&BzK!}`BQee#2>=Jz-FDAs548s}F)C(V|x~HQ5nMUY;7QGntJ{I+%o4z)e?qKmmKR|{wvhi^U zfT<=tBUR@*EmP9^Gr=v$vMI}(>_@Dc#a0Uj^@9`_=>)&AX1r;kZyXfEI!Ck?&_!y& zT;Y0Wa}KF3j%GCl0+2r+R$^(9Q_$ z5+pf^EI6&|d7zX;46sL%uRrJ2&3xcQd#26G@NLEftQqB)A^HLjG)6cT)&)Y2a>!i) z6|wcH-XDOyad{Q8vp5Sm)y_-f#)l=gc?Xo&p%4+kVD6Y{Gbl){d}%rltVJ5xbiEma zxwn`*fPHovwyu4Z0k&d){6VsO9adbm$llW{WuJsvSNZkvFhMN?$P^yVz~#Dc&$1Rk z^cqSzFU=Tz1Oq*<9P1CLb4sXqCV?~bn~jPmJ0|7Nl5mqwEUCRJr;I5CUv;uLLrp+= zhGxLZ>WOX@t0RkEu~qRh)lW(n#7+_|sKqH`Re^D6cAWRi^B`v$onFMM!-)aZ#rBSJktUM!Mu*N#h9mOZki(KnmS$7={a2pCUI|Qri2?4fZLj6fjoLUezJ2;_=Ge=yK6m0H#>Lr< zPMXE|#HTog(yu9ujSsh*Ws9BgZxQEfbkhVaRLlp~gg}(fm>K%n~2#W%w(gOWFniCE>%TP!6?P zNU@&vG?lRVOB?a$I3n8J2*SFQUXfTgX&)ucHFo7f&s8N%C>j=CUA(Ob_dUg)b49Twb=5o09*It=-`?rj50 z5Cir$4FeOlN2i#!(}~({=c`!yb$W?otD+lHgK=(}KDlA+Jj zAZW(@cu`dVP!5@tl(2*9Ga)OI3L3xUdH(#fv!;6dctyxQ4To7_1v2X58^v*N`GBI! z`+6v<{FEzi_fj3PVuKb{o5N_Vv67z4@fNMmimI@YN^HlSnED(gwIGAEIoYgQ*eJzH zEG0VFauh^Y_>rg~4ely9rtH1Hwxgv%;`h~&*YK53B+2&G4;79}{GvE8gN1+ei?Yuz zF&#aPot_4LfQnhdPHAjzEjvj)!_9<(Pa_74OA%==F;zImR8#g0g2yrY$eQ-z%tvtw z9q3K-1)h8(h`C8s=s6~|-;?wjy;zYcr#RS37ketbM}0*b*3WtTFT-Wx*?$)-Hm9A& zi+EIA;iE7!Ohh5LqX;`q$INC@aB*NAhxIG!_)BR-%Q;{Cwlvfr7Vdysd~|EE zAXW|LuG)q7NLcBs+HNfkDozxH`Ihw7yN6=KP^i`ND%~bIs091TR-i<<=yCd<;ws;D zqn0B+H|-)LQk|Zl@gxHOB3?WudFo^9e`6eHPW$;snrq;kz z9|)U}W{B3UN*#LG)kT)vU~cLB*h_!GT4nVUde7G1j>9XA!jnX%!tcl>KuKJpNOUL9 zROCe`!dZQ!y)}dKpfE7Hn2-Y^5@>D8Vv~o}O>uPS({6g-B)m}>VPXCZJefn=w@StR zpm=|4KEcyrqBv`74K*aq={zS!^$(M>LZ}-`J_xutw*mrf&V}Eec!=;<8~z_i%AM5! z^CbN#$U~6$uQ(Ts)snpubqxIZ!cnYIcpwJE`ClT#EhQ(d%PpWP)$Qge!n4Nb8{T}Q z=gX+g z`-8xA7-9S$ZGjL=Exs?=|DxHFQ`JY7^bp+xlp3_)2?1(BAtN$>R2JUlh_BP@E_%59 z;EWS2u1t09cV6{IrUC=$y>x}pLePxh630KOR=`*m{QIXshY4V4%YDN9AcnDwbTxaa zb6zQ>EXvJ{w^kJL7N87@Me?7l>K}6ZraMbt#d8#Q`^PcA@bA)m@QGN2$hngG%#SPw zqfLxN%kCvkX53+NM`>WvYK~SaG(7q^Xv!|1`~NhhY|VNr1btP_DC9rvuHyHNY?aP) ztL=D5!-J!l(2cPd5b*8=^rVP^Jy{H$&mvtcaT6~zB;QbKaOZ;aCW?ZQ%%0CoL0WYT zusDy(8hD4ps1hP7CCsJ6KHEle6GtzYkxva_Rh7G0f{pMd-SF83pRG4b1jjulgmf#_ zj|dmK5!4ldZCvhzlcA+{bs1u`y{HIG%3(qMJB03`mNb6bc3w;jDnoh%Om;Xl7|oj1 z0P!+W+2QGQG6V8heXLmC75GX1A~D(_F<#ed4j)d(zVtn@&M+jQ1Le^+7{!&qSyGe7 z{eF%gvOn~8L&rZzX0cbPHT@T^uWeI|bm2>>Ui4Z|_etfwoh8Xs{G^ML0WVq2AH1=7PA3sA(b0~z6lO(Z@G^4V`FqoF5h6o^14e2+ z=b1HLn{ctu0D3j_|1rz)_i}4*%2=IX=Ok7^$FG3o1f1Ls{Srr`(2G-|o>N2d&`jio z^5PP~_Wdo2w_VvQ8^0n^ot*nf7HDq(vkILmBc;ALx+%rnxmPnEBS}#XH+~<+z^Nt` zBn3k(pzws3IHrJ=q&c7fbwT$l)~ZF7&(I4O(96(5(G`!hBXVNm$6z6gFdzN9YtI6u zSA|+>FOjxX78Te>m1JIo9R4XX0@ye#R68K(YgM_As1OJo%yYsSkfc9*b^!#S%xIc4 zYSNV^n`s3e`?tZsUmJz{aa@Cx`~;Sx!==lk(TNY5#n;20RN zQVvK>(@~diMtfz2DEZHD7(|vXt~C^A+@~E>GU8D>#BJL)+e^P!sTZipaQiuz{>cg* zG};xs8pFkAQ&^+HUm2zEp}Y!e4`&Z8(0S5LC)SKoadV5^SmpJZnH(G7v`BIPl$P&q z@bv<*hXjVlpjQ>dxf+{yM4xehbRp$%jE+jY;*_v$1;e>cx*rLx#49`Oo!TVeVP{}5YH*V1hdbd5sJrz##GBOM&Mex%QKDM6= zjUo>p^;ODX;$w5V5G>OYx8eZE4&j+5OSn8nv?yRcuqJ(irdv4w;Z{`!mHtCqMRN?@ z%g)D%aPrAZ;%ZF#^kkj7C5%c=e@n-LD*-(AUOt1tXQ)bJa~#b->pvm}5C8!};3lsZ z@^aP|3>$xjhtrc$1HakArE^l*#=i`uek67vfs>x3jWLvKScZpzaYvHFg*mq&&8|VS z?WjROREqc2HUqU?$Xbf?N_CM}xeq&CdWX7-1~8ocb>l!p9GK_W6T~Q>F;RD0*A2VZ zNhwZ}qZ$uHzNm?T?ZAAX2bg1txr0XRk}BWc+#IQj8yWis8Jwa?m&hm^eXjCG{?mN3 zY)uz7?(auGZyI(IaUO7ew?2%jG@B6B3!3H#sd_!@i|t{wV-!{4aXyG+R+$teHyD0> zlGa2q+?*fzP+088m~TdSAe+5|0d7x`M(v5-563pU`}~GQ&ZiRyj6b#nt-;8~9Y0nF zI=@nM9Di1ydrZHQJ2D#?lfXGRH8}#Nn0{-z*-^LjjYl+PfIo-6(0ANFfrK)CoBDKW zDUjZpxszMj0o8P$ks8tLyw!+~LwAcs{k1w$C>=eTf8o4_{f()&kiVpATRw#Wj_6+f zNE`B-?XrhV%OXcd7YO813KRsG*`s;M$KcHwc(})1JV7tpYL{`NN4=q8WzF9(<;{-s zK*$$U>M9A(0(Eg%K&{<$LQIwS%d6(Nv8iNzLo0WWue`J)p*5LZdLC2%G7FQDd>hd@ zItjQJyj34-b}nst7l7ZNs$1d5$Go$NzLfZQ_o9ufNS!=RXj;+aA5wzOvEdYQCz=!^ zjzPKRHsOKja}wFE7m)6iM@F9)TC2Y)w){AXGL; z*w+Y7HF+>|-zJJC?5i9{K?%QPBY5+r$eh`>m}rL|Eg4DU@Qu-1@T91$V>Tq+zF?;Q zK>#~|4I|$v8uqAxk+&RlYFYWs4c63ll8oQy0E+pjvBYk+L_Lf{-ET|;Z;=_(+)A5@ zM3Y~iw*|a7=siAJdJJ^liuTJPQ$hb#H$C&GNp3-4|lLq^S81OniY(-UogLP-6I zH+oP_Sh$~j$#3%Un3wkuc#pNysS&ugxYEqh*lUU}I-SMkv{94$81o@2LQ{t>O-Ie0 zWM>WK;f->_bmIm=Nu8-G9(ij@2Rff4b$eNcSS9@mgEw_ZX99?0NVx{I;Q@ex#;lb zK$_?4aA-L)bRH;7rG?A`O+{PMVs;CH>so%)JbY#i9QJ$a-oj6%(}TnJ(MTrRgROz8 z`S2$-b;u|I%r@ZF^vzT>IM9IgqOUwXI0jTa!F~1ss0RGR--8hYUkT!JzUAMDIfb*j z4#RG_R>U3T(hqdtHr5Y8hqOh)50bP+2#I~w8Vt!BH)+v=uYk#pt00DrG|u&HHV?|S zrrga{^hY5X2adM&*8=G++AkYTutisZx_bMB)+V?=ezEyzO8L!E_I6>aN{2svU7OV1+|RQ>k&e;{1^*gy8N~m=QfaqZ+>=H_h=Q1sNH~HJ z8z88s-o;^*4nV&8BQsaMMqkJvAJh?>?M8kJh{#R{?Z~E zr^IY$nW44h#LeEl;<_R1%ggNyKa@)D&2Ut*G-;aJ;~u+wzE5ljgX$F4`hpo zZbI4(GUycg3U|LAZsgGS69c5KkZu&CtegBKR08}k1?}kCYudxP6aefv9agQAx-Tg@ zpW`X^nmu+Pqd3)WHDk={EVx3s!N!stCWGFfLy&pS7h#J>h!aO>EybA3Y2a zm6(P$y2hSOprXrDTkMW;K~os`AI#k*-DRb!del_gwg zSIQsQv{@RGw|y^8w~7+dEx&~yanSHjap$vfx$TxeEgp;`$1w;{rl6ImDJ(a}tX_^Z-p`|9VZQ^x8XYZY{(ZN+}M0(cmAw#@)T zS*Hto@5Y}Yeh-(k^&t|vVKjkHx6g)QWQC(c@uWi9-x>h%K>bdmoBHO(hqs;laT*pc zGlZBGDd_J}dg^9?53p*lfIXh*-gE{smf9##=&2&Z=sQKMGcvo8HAL}PQd)CMHYUJQqah3G2%a?K8b_{pa5<6SvW@;WtpH(6{nyOofC3({&8W- zF`*IS*=-9-48!JpL#wvk8IubG@)P=w?CwgEy6OvrkmFvy!JStsfD~C8avpLdw`&E; z=#0z)JJG`8aL&gQdDO!jU6+AG`l_W@@Ys?m-!t^x(Y{{V5_TXFo4Yo@<+U5CP29XC z4cC$~5EN6s^y#dAi_y$!^Ya4ov2ht|mEslYU49rD)AE@q^Vg+xexig|q_~ypA^*r| zM7;C!2bS$YtDiX9qqejc?p63|=h%Vt6(?^p^w89g zZ_2+Lc@w^Z3_yA*z`sVg$`iHa5<2iq<6LMwLp za-c2uH4lS93h=5cGQ|33x->tLUC~*x1%*d`<6$c$SxLI-wbc{z=HiA!-l#ZcXC97J z$veZ#t&SVHsgFF-7<9&XFFj8m%KlxAJONg*PMgw~7ko>h$z^=fS1#D28Qb0jK_9D| zk~6oL5~^H}`!-e9OMar^@l?5DmZr(a&Hr6MJjzIVZk9!EPs3TuY9Q8`y>j)rRfgCj z1RruB-}bA@HL}X}&7rcj479NCY~$FI@n6&-E}hQv1;tOUz*!{KBV{7ly~@*&#nv^q z0GhCdY}D`1N!|j<7b{FGit>eJrKWc7%%P@xs|(l}`$<;R4^k?!E@00dROSJTeqts}6XKQpWiF$?7YtMyWzPVhXh~|&NhQ=;H z`7Gtk*g6(DtYfPfDLyp(7O?x}NovEudEd|=UqDdkGfGyKccwgyhh*kZ2}jP!VOw0N z?p>21pHUMtb6HiCIQ~MHlZ3-~?!PGe7d5}7H0A}fZMYPK0Sa!50bZ=>tD$=N`IzQ% zC6*?UQ#Pir8Nee&<=l=@si3$O4doM|^r_F`qA!I1BtKO*(qEe)zA^;n*2VPPKd6Il z%BD4e-AM(OX~56z4DL389elUVyYu%H1k~ei^~?G3-{(Pk4NqBo?VdurJW>pGJ+t-e z0Q05~r|(19R#9gTx#MuVKne_Cj$$E4j>ZvZGDj_-Z%}KC8hACbKg#sQo;KEB;}nE< z9aj>RYKxg%+0IaT0Tug)9E6(!&f7>H&M9>&KE`*QExF&C^cE<{6>P&=KH?fSLsf#fv@Q{MVhfK^%ZBD1hE zfdij%$4FXQ{M-u}`$YJh@y;fh(Gw@gru?XRnC7BQjy~g2GKFQ%+_%~9kmaCFx?S^f zNyZ=K8qvspDzKbxOnzHTGXn|+o}j-|;yVnj%Xvx5vH1EycUtCGxn_hX4}&#BrQ`99 z`_a=J7>>OA$CM~H--TNA)Jf6tDa^TQq`QFaIN#jn zABJHr>N-_+16A_==|3?rQ|G&upM`Q5RxdKOw20Zy=Tr$57h3>TK&rov-Ao^8pDnk@ zbrkVU*||_=b`g{xfy-Jpi)a%uhmu|PD>)iobO2%1H8;2cSJKr3O%PY`l)!8rrF^Q6 zj*ncR=9wi0U0FpVGMNXV?I{?icHr#JBNHE-p7oBU+l>r6Fa7lUhHk!`F5PT~EIuJx z6I`T?9*?2Bu{NDUm?Vnv3CG-xW7=--#Q(_6VI|zp$yIrJch<4Cfcia3bd1MaT`M0< zP#z-Q$Ua|*+|4D)s<+C!r1seWz-}Y5k%EZy;}n_ zZ!Lx5(p?CGR`(h!n`$<>+QF@C;Cszdx*-s66H}x0jK$0N=6jeJj*z#HhXZE`R!rI%i^G;=}GpT@^)VKZ9RsA)EGO5M+NW zkP!FhK))s9@a9ack&S)nDV9e-z4ex%r_d$DZH7m6NEU9PQ7F&K`{}qJME9=$x{Je7+R)#TL0#iW=3ox0>nM~pa zn!w=WI@r<5fezj+-miLEk$bVU@oKbiyZ^YJk^5th&3TtMp4*)!@<42yd6G31d~Ujv z^}v!VEk~x7r4`crzy;E2mQ$2a-ZY zx=Yun);$URDu@1Xs3S?Co0H#|#+T5?)4>};t54-bR}d6S7;^_3p6oIO&k>cC360Qo7+~*lT+SXBfQGs8=bOT6km;>|+H z>q%mP`raRoEqU4^KZElKPxE~6(({Ff)V8)Te7LPgJo5P z-c6>h^rh{s#N0=-jQxwfvDEFYZ4C26o+oHpG4XMmkYgOF^PuTR5&#;REp2)#~r45TE z@<^3=YHjQ13u^_i1S(}olJ_rp5Y4L1)h8_by7oEC$d_o?ULg0!9APCH804t@4DduN zXAgjrmLKrp+v;zgEh`Y@T5`ZhscshWvV=wUD)zF}gcNTMd5ruH;mFg#={XRHFU|HtrfXu6q!$Dg{NT63JTO*pu^|~Io8JtO z^O#Z*2UZ$y#S}GHqZO4>){Ch6WsxqrOx1D7lvxsH zA@Z%(i>wBLi7b|a@^KZ50ch=$QpU~PK+~=tkHz$R{!J#%j^vZHaJ+z;QNHBHhi@;I zsbmXI9?=8xf{n=9^I;B)DdSWUIaHly6_5FMSR*R#EY%GcG!=Gk1TMVLL=qHtWG_Vt zx)5S|YxD4%%wfD&2{VC0qU zx(f1V2kMY5SSZ%=#u-zZcA5zixdh)-^2@HFY7PkLR;ef@Yy@3LdCwA;(Um&bcebr7 zeh?MHmYXfn-{|>K07J9$sGVESa&D_Spl~@b$rQY?OVE6lEP++#Oqnm}A<$F(Z#({) z7yqx>lLeZW+I|6KUP{zL}_h zzZD)}6>)Am!*%vM5wHKQOW8V%3Z|u}G*&}0BBTHb7 zx+uYr7a+B{vM;J4Hn#;E^s&Pe3f_-d7z>t#>N-B<9M?l18V+~a<6>}=B3Od=8Wk?noHZJLgf67em<^8`>QJykXGj6m|g3 zm2_WqH7GJ%n^~1*g*mciQ0FRt97#haDN-`ZOnD{wXRZWmZ11IJzHOB<_|9~RX0Zb87+^Z z55F5L&rG?xutXH0X&c}TAm;ir8Jasgwhj3A$Q#K9Wn22pqWQ7#nDYVDb=4#Co>Z7I za^mGHfYBh(E%@<@QT0ZlYy|_#+MPV1jVc~xvzS|K0YfGL4!nR47lhWrg;WD9l_Du@ z3=dxy3H5zDkWkbx#=HfQh->8ooLg?slUp<%C0bGyUR{f7c5D0nSa6l7??GL9)y2O91Nnh%LEBa$z-%Ey#^b53nJB1tGyshzJNOf;@ne^17Uuf zm>W?sF|T_1<@1|Pu-(=!%icYA$8ue18bonbge_u&Tv?qtR)gOPwY|&LQkV2O#}W`z z7`k-ogEOVLlOsCL5_4mU+?-6{t921-{c$n2ec7@XhS$#t&d3`DV37HWq`k62>$b*E z%tGc>(yx9*bOrpN0^Uk`BeWX`c+Y1QM?Xal!Ht1}cE=n~55}|!1S;G2aY`rSSsjcP zX1ti|6@MR2E%flpjgQDdoM&J~Wmq|(G^s6$f25ofIDJt`fJ3g7e)-XoBo-L`W=~A% zd8zCmCvSgXT@lgOkxZYCc$se|#qA>q=!Quo(D4mlKbKC!fPAjgIw2o85GYySpZZOz zrZ~2Qa(+Pt_|+f62b)47ENi%FYod15H;}dy3;r>ip8TLq@H7uVkk7(lA;>WNkHgJUk$OTz*4@0l>5bX9tSwu!a^XEam(~i5m zIp%$0+2fPZAa)71x-otYo0fC4mkVpjjy^M(^`)CF@0+V-qf$Kg)nyp5O?V6swUjUmN4**TCb&l!@?S z818bUN!xh#Muzr4za|vcwSZF^28v$g*@hKtVL+#!h1VTU2bJ8qqN7caJdIL0!?hYi z3D`&Rk#8+xE1}0u*M@Zs;dO*gN;CG6Vdp3!D`2xc>WNJZ2E_P}m>42e$Mnh^YfQcN zD5FS+>j!h0%7+9@S%IW7_<5-R`&*olr53g_pu21%dBy$zU4)fWT5c{-`9QsDWwE~) zo;91A(VE|sbdHvc5*t1ILpV5S|7`t%?C&4*72f^N7Z=7aspHN0By^GXC{AWx@FNRa z*jUd3&1-%XPi}`_a!UKyIyni0;e&0_W2oI{*E8Q7e$EAhxF4IM+EY^tf0%5GeiX|n|u zq3Zv7PiGVL0Zr13UN!V%$jnz&+!xsc=zqbKY4oS{axE0weK=N|gJ^_S_r>xSqG}jj|my8a5`qRPiD7-ypUZ%^s!GHCq;CxlniW zuY9@u_NcVp5Itg?3lk*YEG7u(6T(<>4<)md(li`zIb-Ahs5H8+%5tACcTZEVudO(v7^2SyDy`jC>Var`t=iQ^q9LR?$>(>cy*? z&=nsHydIs4*qO|1ZSsb)4V08;+3c6+VtCofG83$}qqI3VPeSqM4vslV%GK(&pN$J( zh;Y|kOo+Vm2j;~`aiyWVOi3jjndMc`fu|?W&JuXyz=T%s`3sobHdM5r2;mXQGaU@l zk-^tX)*`^asfmuCbt*1lB76$ot1fL3hiULYEnO*y!&cnkjB0}adTOOjlXB+;)iwSW z;B>f1+}F>%jvU`V5lx|q>oQN;$zQ}PpYl@s^6;k*irjLLN*P@?&RF-d@8kqH%GE91 zwTAr#CS z<@WH;W_L^-B$v*YZg(7wpgs022z5~&!hfgk_vRP@GK^umMYHtD5?Wddq>etqJ95(v z5>9sXVJ|Q}Q4oZ{xNv(v;u9Bv2$>j9`sb7^2Qxw#y~pqlKLF%8PJgPreLRATACG3K{-2W;=b{jK)-lx zwfG@2E%g_=q4~@QYoJ(Pe7Dv|jom&nf8Aw{#@dC5XsuLtd5Y7@!!jnYGe1~=4=^oc z6$IW|(kI7JMEtCogsX;{?1GMCJ2^wog81)f6-_ezDs^CldU5p%}x#P^oNuvMw)uT%kMga}SMT|7C^5ToAdf$lg6A`8x;1?zr#cr#aA*+dYX zm(6b15LK_hfay!@0Sj%6<)!VcbXl{3huk9q+BSe3J+al;mXy?foW}=JIh(4@#v%pJ zYJ^T3ntlMT?E8<>`V9|1SPy6|=i6}3Ev#(~pzOf8Rx56=or@K@L(!5dvqMJ!5l>0n zj#cP%=BmmJz%$R_LY-roS3G@>x{l`jg&!-&Xc8`Ws!nB z!+h>HVOkckf!~+jJmd?N)?^cNVQcrW$F{T5xlREM=B`?k$#O=uR)(&K@Mo`YQJn_S z8hckDLtETcVwza5p*NM_V;QXxRO!l%Cv%X|1mtnv(sRjS_o=Q>g_J-+KD#)XZ~<#n}%h zZ3HteXvwu713kUhu*xtYENKJVm*{ST-aplFym_@Ox~gtgb4PrL>Y`FkjR-HnUMu(9 zTOAMWBM-u*&X1_7Bku+>eNbsPoENfC`MPRa*%8^mco7lkGDFLak&W znL-GNj<Ec36BySWWl1n*C zDul@Rb;-G*p61Kp5R(ZYm+igDj`b-hW(n_#A5vrWl5_L;75Id=Ar33)t$+p9`+Q6& zchKV@wOIzfj7ln`*iaDc1@MCt1J7Pwmyd|@Go{*&fj5NKw8dPIkY$YDmG<$thJ}Rv z#h?=IlY12rR1MNk56T2?l3a+K#xY`|g5sf>&cXpYb0L}?GPlsBqzfDe^@F!5gyN&1 z_Z4(N4=8rIoR=XzW=~TT6@C5{XU=)ONvN3m9}@OA|@IkO0v$ zAfL%%y(T{GXuYS7{aQHMIyHZ5j?Ki!%KOs^JYDKDN+z)<7Vyx@_G6Rn)bXY5a&TI< zT?!hCzYq*B-(!n7!7)>dX%b;UjNH~(Ju4vu{3_8@}l5K0{ ztzALoDXCerG$b{X29S8RD9++##agS>JwrEde$m2F0n+x#kE%dTNG!Ld0Dbgl)A>0v zf|?YOznuDwajm~w%Nmr?7aqere(BmI8zI--SYf3BIW!#?*&6Q2r9_vVHO(HACQFHy z2+7jl^jyxdZ-7QZs!x-5Nnzq^q;b_cQXU`cmGVUpbAH1#K!i!aNl@Jx2r^_L5vwHv_oR4v1-SS4SgD%;s zdfnXdzE#kI5`fg_$UkJ)&TekY;j(WaOdWafYg)JnAN9SZH00{-V~D8o8mUK$RmUb6 zA)AroYS`1t?wx?<&!^bDsUXVKR_G%go`koSWKxon+%4{2w{5<3&BBjcIT6C+E^Uy8 zdz%uett6tC$jKS?*PK?1R8jJU)4)PCB9z|K4&f`gVJ2^K9V{AC+@>=3 zayS~SRm{BgHb>bT8EbT$?yDdovUb>L9H(tFXXnl^xeef8D&*@^ej>{?)$8uDOGb` zcc&cfMK|3oE;}{1-|vul?zypYGFPY%Y5uN{k;{!UjJ81OnlUO~wbUIqea*qJtHc(~c^f zCl#*d#YMySEJMel&fM1h!0I+jmefy1{ZG5!o)La3G!QMHginNR96ykf05x;))~8HS zkKpbbrUG0;Y;)A`$+p_Gm=-&P;snGmdYh!KW9$87Fsd-^h)TI)j^>i^^oJC#`4)mj zZB(fY$oFpcpBpe>{X9&P6MNr`N@LI1NimGvhK3~e6na4EdJ6wT_HWUt$+doz`HR?z z(ph&F_OXKvqAc|6b5xtbXfNATd7=%{DmMhd^s30N1#oS4Z13 zL?t_pBRH?AYA-H-%)jAI<29lorYRw0``a>NL2K^|Nr!T6{771O+eW>BZ_{2|oqkN` zW8Mx&O^do1>K9Y(x9~I%%x*E>K*ehAFDvuW;2nzPa|?x-kREvrD5e<5!21-xKH9g% z;cFd(a_%mGt;D5zE%uIP@39-LDRN;ywhpS5rb)0~#BNEMk@yFOWA6V{9@VAKSLgBh zgkgd%%q@b*YB4HBmJ~0jqmuS%XaeL${H>~PD}VoR$|uYIluB7bF(}ekN&v1~67Omn z{G~Bp7)|n1^l2$d&71K;tG^Q#)y42Wa?Y^z*8(JU(F*EMMAP`6y+CaVxYALron|sp zUH>>5>b3KO$6ks+${mDcf>Xsf&|*IXZ;KgLX=V-GRT8ywK=5qX1bUMdJ9I1v+?BZq zzI}d;Cp*m%x`+&VcIVF}<_c?q-{Rq4`PE!o`ez_&J zdMF3RUB*AS`kQ+eQpE}|6=m2yQZffpU2^Vg+J=iQX$(vvog_N zqcXBt3OQIM|Ig*7LD?2)8&PX70Tc6=T=%M-*~ThEi_fc&FWjJ-J$bjo&w0DTunB;l zP8A_z+qQf49OcC)+5;6q9@vla-K60!lKt^1|3P~RC;?CRr(Yrqq+4TDD(h@C|K7`5 zzjp#)hmIFY9xUgO=Zdn3E$y&lHfh0XO(lD|JmtG+skP0j)hrgRxGuI}F~h`kIl&zY zM@KwMJ>`-Zy+S-uPetXC>Pz|!N&$&xqVpwfFIbJkf87 zuHZq((R+)wihj%#5qK1DAS?A`OD>zK{muWRbT$GSmujyz7-FAD--J?aCu-+qa=Zu9 zD{be?z937FMbMzft_yziK%F-ZVWuT7iZ>>oS0>d~uv4gPqYE>;5C2`aQ?k72G;{J} z^FJQ8$~X2vFG$SkBJeu`5Nl{hqnNGE>>rH7?$!V0a5DcazpiE@zzj4L64lbHK&Iu# zVca*?aiA%L?;AG8-=jd0R}!BETroT{6oL+1OL1=2JQY5A6>I@L+=5(b76~pdp>CFB zS@;3&T2D| zCt}5;8HiPc8mQ(XMT^FUz zT$dLffVf+%1>;}!Vv;~l^Rb9Q_Fh*HJy18=M^9mAX)P*w@e3e-FF3=CyQ;K(VA{B0tqk~o}-!A9w_z!co zj2WjgnRjk6b8mzaz^}Q1%03)-lL3;#VH|vpDR1u1>ewOgM!#Bf{+6}hM*$Bo*j_pU zZ1)*?3LS2Mk_-=jC!U_;#=qo*I+vys2&{Dql^3>aa&uDlNmK>$-VX|a9vE@%9^Ct1)#EAcX%I{ub2o5`ng0bwj1yBPbuo-7@&p)DnL#BBky%@!Pkobmd>3~&Z^rU(Whkeb5Z6Wufd=l? zz4=xQ7F`QGR(|p31-@t4<+Muc(P0!`D8|_n!5;AKs@4Jei?NpRueOcWTgMZ+*4OX= zOmVqbf#2Ax$s>56!CROT2!_O*2f;tH?DOoH434B!Tb3_yAxGy#wH+lQ5=$v*zlU>v z&X=*t?@U1`8lNQAfrWfAdvbra0T}kq`pV)rkX1YBi9hCH9gc)hWMVFIT+hd0zlccDiNur-anfn|y<`I} zL#hnkVNFc{O7SRpra%}B2AgJED0e5?nf$Pk5n0*1_bC)}R=;5@!>30ns`UzV^>2sY z)9;)0uiz3NQxBDhPi%2gb_(1d)2mE*su~axjq?#l0O#o>|K?$>-bWrUuZNYd^AcQS zTg03E|7mx*LQ`oiCV~lq82z*()bpb%5^f#B8M2;43@W5;QA-zqcchglS8mouvzoxi zZf~Mpg!YI-x&{Jpe*7(E&&L|n`M^(QC^#!KH*QOSOL~z|~R)T8~jp2_3yZA(XmjYkAI#VAgPG{*3 z<65di1(fDZWRLmC5Wk$+XX3pjZCc!)*NJ_T2uq8b;VSNYxSQ_f*V$_5$m=%J1OG6K zf51uD2F;p!7quOMi@`>&3<~e9)@A?nx^whxC~B&Igt2A7L981OU7DN&h<%e|8=oc| zwLvu8p3G45wqcudR$*>E<`r7192Z+U&aDo0$W^tJOB9hd@f zZ+G2wZH{mNCU?4srr&EPjeK-S+9$r)(i#PxLMjlbV<|=It)A9@4#cNj!{(!zS(v~z z&o=e>vwAs70&Mt*VYU*Myy6xUPHRXZ7Ke9!yM`K>cFf-jaoi}VmQcq)p8Eg0~J) z*`n1&|BNR{oG=z^({jQROn<+{oFXjoeu1;}@q=UN(N2DgcGB^533&xSt%nu8WVUxB z3YjXvv6!A`}+)QOhN2@Fn@Ycc8$mEr`gKU*^A zwpU|dxs7djjF15MQhb_U?x*%OU1@QwgPJ85xi~ewY-x1MVY8J2YjIo-uwB)Ac+~=w z<}OjJL_e76cP8Z|41}LuzNdt5_-~AV$y~fpa;lE~*t+5sq;P4i$8dsU|Ldj93VN~f z&lj+*yK)6zBrmN5DkZAUGx9n6g)|nTG-e1<@~u8oYY9m67%I%5H!z9yfs8@763guA zwM#sTr7e(u(mm8b(fFy_2*-TwphU*95)}-yEN>PRpLEIcnBWWU$KiaYV}wD67!>MM zaPe_=dQKENEl5%W@Mhp{2=aS9iGuYe@TNXw2vGfeDRZ~BPv}aIf!FWIy%X$1;$-a& z&NUAE!g}gs3v97HpSkIWe2bo6Igg8AK<~b%`$GVNV>naa0rT4@WBW1}bTRGG?fm4- zR9qbGZg*dwNK8R?{|8sB!}Qk*-3JN|?*v{ym$X@PURG-5u_}qV#+faEmLuedqvTm2 zmXZ=-guGbr!FIqyHhsb*B^tMRGDz)$E_c}-q3(uO&tr2ILP$kPG$7}L%32c6@V#e@ z;g88bT8E(qH6i5gn7} z67o#iUrBL(k;TGP;jq*Bs^;=Q?zkBMytL+ChgVhwk!sm*L9NfuY3<(%Qsc&<7H^|W zi1?~)%566|&E0GbrCd1C)f(eJh1#vaqBf=zGAk)^eIKbB^A7r#(rt`+$Gx!mf6QAS zEPYY>TCl%iJJfaTzqAukY^KPAv1B;l&0A&X-tIFbnhXlcmcLg?JNm;j`U}r^ar_V|)Lh_A7S&J;}sz#~_Qv2s8VP6pe3S zUKs-zd!!3^*^xP;i0LLYdf4(7aI$+AewvLc=lh{_x1q5`UgVUVz3!vX4Zb-L&y=iC ztbz0fyp>RR)*QVZRKoRR)@fyf&^z2xe2tB_RujYRS{M8)OsGYD`Ci&BLTei*%a-%j zwlV*TwvN#LL+?XJpJ>rH^sj)Pf;IRCYe1lgYZu%DlTK188F<3S8q?0%1c# zkZ@81d+3IU0W2P#`^A|DN9)(}m}%NI=d@ogp+6HeAYXH`a)tC@S3^=I*&_YVC*v8{ zEWCRx!nPF{@eJfqT~R3yqBr&d=Nn!HQU>Ohusdva_6;BNq*3&SLtLUC&EWyjZeZ0d zIkOoyydNkFM2`X;TZ*vMp*C7SU%i_c41QsSu=TL_#;?IY75j)opTbH@@pC{nKd$b} zxoT`4FbXYNES1SNyDpuzBC~(-q(TwJ!(g1hFFeB7wkN7*QB85(xzBSDkr9^>=?xW9S?#zJ%If9zyc*4IUG}B#eRA_Q2=R`(xV= z&57M)eGsWYpPJ_P^#W?>kL2iSVuSR4+gBy(ztO)VO`%Ml^P17CanL7^$A0MF#O3h6 z;o|n2sN1&4v`+*6WBda`+m|j7j)}FuBB;%Q@}MGY#8Q+$`rLFau^!>GqtM0xI3^`a zv=%VBjB;1Cjr?T!DcbHQrREgDQKioI*1L#&3)}rs(0R4$D{T0>?(RZ-6_#sBf=Dx) zXLZ`3L;aMHPY=jC-dWmJtk#F3am4B^rBsF1Nqa$1O}K5Wu|ja*qC*~6xBCy#qhAnoRy4kqy0RVA^4t}H{Mus{@4j+S@GpO8KDQl^KpfPene-&xkROki zF`$tmdPA47oKLU|?_Zi1I~H~8g&Q_>Cao^;w1}(SCEUL_)h=ynNq3sp96hu%5~K7S zerg2a+ag^f*cQv$=;_nUT_BsrCO>VJ)m`@?BZO3i?`m|E^~U8DLEDl&5mqd>OMIr9 z6+ zLt0{^wcj9x<>OKql|MK6vAa!KJUjCE2u!+(5EL!1N+AkqZI!kVfHnPD!KOu5Q|uFw zb8@wq$VyTjmtD3301wmssFUc!4%@Phr@z}saY{`uY<7|<*mjcsikrqlFv8e|y}?T% zY$nsbp)>k<5vG1J9r!}<#|nLJE*51E+1;yqdby?NNE5%>QtW*>Xc5#_;)Q9dC`cD! z;H-6i>nrU&KPR>(;#bxkAL3buZ|lxloX62)T3_o+AL83G|Ad1+R)98f1M<$RbC%NTEGb5A z-1Y+2;u*eX?|S3k+V1$Fi5`BV2^b2Bg;A+emXOt{OlZta(PMTrp|O~rXD_^nqB^8I z=UM5Q?RtjQ_pn8f_w$Kl&W50O!)uQ?JMOB^Ali{BMQ(KgYr6f6p>OI_UzaP<^=jIpZp z*14s&=*9uvV<>TkGI%T@`Ib^9*qTbDk_^bl%AQprCzGrsoGna^iWL@sg#0Meu|2RJZUIVG&HznlbZMsgq&!mIQER8e`^kez zURm&pt^=hW5So+I9ax5;2g;?RZSrqO)Q#U2wDBDH<5`VD`93*kNJ# z`yBY@mvq`IF-AXdzU+!&R zSj)JRlP+zMdoYW0gU~4*2G>fjc!WR#kl%$aD|^iBAgvl$5ipr7eAPqC)IC&18PpP+ zYxgpkwRY7-DudB6h36%_T$i%%nU`&Go{?}*yGOH=a*6Tske0S-)lY$tdXEb9GhuKT zPe(;yVskhOPX4t;?X41|ka!C8^kj5eQp~cG#o!duA)?I|?pG}l0wBDScNOk~(uL8n zc|#vdS4J)QtRNB7mH5DU_E=F3hsA0F=wYX#bvntnI8hg*QY$m0)+b3QGHI|PLI zIi*K%!q+0Nrt46FGb>ynvc-il7+U%CMB@jyuwT@h} zF`&F<+%0@^4SUXpcB~}5dRV+4TPA>jNe;WEotUcq-HM_v);XP(L(tT?L2kAi!QfbifA zP-jN!y7jC<&={PE91Tm~a!>witeR*RjDUIWdOT`TsT)3veGsSSX5HadEK0F0GG38) z4-Zm>?%c3!s<$QjirF&`&1c%>tt6VfuyxltTi<|mKA0SkEJB$)nGcpK#;T%?zc05W znj$%(q?0Bc#zZVhVp5waY^wShN-N5XPN72U%vVOvhh$tH>=qAFzYGqQGSNMz5hME()aSPe@Akt`|K%|Ol$h0D{W7UhpO%; zMFgYA`L5GPn#2K-wkt}ijI>(rpmgr=9rDsf83oN{03xkXLnu4o8{eN7L=oQV$1f0A zE&?CBs?_oMvdf<4nUd3ACPmsHzT4+Dn~fhK83}aVFw~fZ?Sx}>0$0fHvTf%{>_d+7 zdUu)7Uq%sbM?%HKUNahJi$Wkby0`I4D29r}_io@Ed-e?j6W=w(nhm}~zdW#vxeRa)Z82n=5&L7haL>#iVYE(d`=^g zq@?EL{ZjbeHv$I5tEGLxz!Ki1H7*xgLtAA?$zP$iY@X~QkWL+|S@R9J4qxw{_h@QN3BRLphIcIth{0vX8kHb)p_JGqNea=fRG}{V(6ju_`LnBPT z;ac%hYDh+??X6k}2Un-VZFMdzjsAq}IVYz)gDv_>{Ki4s=@)|rViDJjvaC8+0PNxv zd0t`PPN(Lkhl}!tH-|Alx11e(bX8bKJRCY{NsCpaXT|mUl?^`|kzU(ja8BC+kIpq_ zlFVVNhae|l>j?vg)Et?7$T9X1_c<|_uw+H2Dy51{5=h=CyEM%Pj%8*2R-kw3x7l;%W#)k&yUnvES%Siy(s9>7`7!(^ zylU}I5^2c=^tHLN6(cO{rIhxe0ri_ni^X$H+pR6_Y__DUN1Kf3F4i5)J~NmA9X*X^ z)f*rFJ&I}K=1ndIVs_|KDnGdrE7{^mjUmG`@`HnC+Bt?8bM^h$`m;pSfm|qYpAd0f#keYi)cbf0%*ORRcfMYK5mAad`|3?D1^>Z6|GP)UCz(iWkg-nbY9CPSRzrr zR3&D%0TIfF*O5Jur2H?l)>Pq%fu{K&46eYlElqsb^^ z{;bHyl65R|)=nkoTji=0b^H}SivHELpJ|A5^c}!%N)R8LJhgw6R{%Oh3~st2S$pr* z-S6fjA3aTc5(_z^ta;On#}q_~SGI)s`8R8Nz-ojfnbE{-I=Sk?eJuprL}rXU_Os=r zRs2uCBxan<{UKc)Gddq5Ni1pWyeiT1d9(nQeWD9a6)EEf-n%D9$q;>I@Zqieh)f&2 zvXn}D9TsY24~HTl3{z3Uh8RUWLlGpyCYjbo0Tw!i1OqO*J!&dPsSTy^Wiza|B za0n5Wca)#jR3q$k!#?Q}pdBA}qWIrqqQ6gC7~Fbd&=O?GaY^6GVxT<9MmaC0DB%b* z&%l8K8?C{?i#%k%Rc&ggdi!88uXhI`Sx59i#WJVXX1WIwGDsBsv4Y>>} zlHG`A#8!LE!s^^k{GH`+6g^G;qTu0PI=Hr~_0Xh-S8FLm)a||v{joW13-R%8*u6?q z+lP>VQC7Iv{%8O6p1*e1iv<#yrgwt@Ch#C2Nvh96w)m0$9%0ont0D72R6kiB<)Xz_t2zjHr1_Qk?tAm{BU6cPeNN2N|Ib@vQaC! zzR%f5uuJOHt^b{;^QiQg!Vyqj&V9IQ3zR#+qZIB;D$@Od)~alHM!rh|GR^fWNvbS% z@=3pxM0;X{91p2jrMu6Km|VBQO~#6f;iXhi12 zStAv@8OyUAb1<1kup2iou4r&Ljpwj0}X07pQ$zsTx!tVvu@ESdH80C}z{hSwu% zCbQ=q=|A=b=gHBMahGGWH5!e$!p*{0&r)PP zl~}45&N$yF44IDZ!ySfeXErUru_YqK;7qQ63qH*PjH&gY>XGDFUA}X}d)E;M$idqo zrJrCCsDzppjma3PqZd1;tL9qzNcb?;lg98T1F;0`JKl;jn9^Hww2Hnlu?XLq5kx%2 z@#Wsy*${D(84=%d?}+24m03Z6y=x{vUG8y2YM%8gCy+eiY8F9UD&mnEz%rRBrD(-z zHnx7$_oWY_1bC}7p-@NOEg``^~;APU|xF5mlqVnY`d)<3*|Wf+Sft z#{CLs*e*ewWY_2>VLL_gQrhPyyD5}CX}K`_ndU%(J_Rx^`rB12>4$@n(uujl}9ap%@SGQ4R+Gu#&Mfi8w}!> zSKxw!(mPhqp?kQ~RH}Xu-e%?bOLy-{!Eiz11B<3gRN;XNiX6!XDgc7B1J+!y2=nbg zcC&<~;Et_i3hdKcZpEsN>`*&;xszUYFVmM`tMBK8WDHQC zj;mTSis~PX(%$@91^LhwD}~jB$XROFA{z7Y z*fh*q3TK1~v>H8t@lUylz4Nk#y($6Zv4aBVDyNp`pdZPh@m**AfG`{x9|-6x7qZF-e(AMLY+O--)1r)yCUs zfzgB3b$HjCN7r#x6pv0#%s>cP+}hJTl39qvTd#c^NEr1iAtz_wb}$oV>RT2VqaVbf zGIAD$P>TD8+@@B*Uk2+4If{W<=6elf92voRQ2C`FNXH<%-?MX$9@7_}(y9Gv37$jc zZcx%MRpKz1ldY>4itl)X$E+p!;1sT+WX?>=Lw$D0CtD-B=6_S{TIC2W)lw2QIVQ}P zc{($SKd7x1?qY1bJWHZ&*GP1)9`-b&CpPFYLb8bIs_>6 z0~zA+g6T#+;xe_6+w#-zRO3EX15F2jQE>$bl4-2T2ksg@9HihXXO&luBU?>DvI0U6 zBKf#dlHPMSTmqnwS4FU6sj{X;XebM?O+~@FxTP)OlZkl5pRtD~-_@+2fM$g zeGSru8GF{zK)VLXMm|?z*{iE;uoCY`yUiBLDwfgl-(HwLqL+%WdO^U-pyhm?W^)q@ zfNL;E)O#ssg{ddb`DuxK`!x?nJ_sQh#e1keeRhqU`|?#wTq*P>1eyFWYJ%9k>g3!j z6I;#a0li`&Ny}gca__I+w`0qS%aGx4a8*bZENUDJ>{!kv?)}|@xN=M;bUU69+>u_vh|f%&oo^TMuo9FYbnu0IC`{~{NLWi^rMb^)XAWBqgxfh}Vup zCcB$)!zj!gy-jjn2(-(>c8TRDmf@%uphixKgmuU zxnm!m-8fN|;+Eaq12=!2Ztjm9np4Y*!dYq2+$=TQt$5SfN1|yT%%(ky98`S_@=LJH z2rU^5wC;Z!{sm11cc1&jag|$E5dV?mfc@AeKDd9TxfKYTOqmP}vlcFH8rE0P4hodt zx)Yd2DjX;E3fmN!EbQSlhKE*Jg@>tc#$^6AU%z>Kp7pm5Y<%}n_a3ZN^SA-T5@q=c z!qF>J*E+IU8#k{_+ZWb2fPG@Ci1GI4zh5-jA&W2VvM8eboBfRv7vqgh5&1F#ZKlzm@(sXLemlnB3VDF^f>F7mSnk)+C*5Fq7%-)xe`u3bxgCIit|XE z46gC^3Y$B6^RO{5B3rhW&JLW7h9;3i+iAPn(MLaIbP%Y`Qv5nxItVhQXqs!i_xqd2 z77i{-c1%oXgf3OaHE^2lS>dzNc2o}{gpv5hn^w6pvUCw`Gd(#{%(iY|Zjg2(d@RfE zrkdh;X&JE}yri-1cdNiSb~2EVGL%@Eny^gUEqu2~mDkP?9$dUG4)Z1CtoQrV1DI2! zcivFf5bvNkcUv-TDkAKTtoN|6tC9FnPF}#|)qWKEHfl@rG7+OqtCSw4?c?EDGLp+= z7xaH8*N90;C9hwl&QzQDs^trXq+?2wkG)|)7t#oFjBHyLC~xFEid=%n7L;tbfI~h6 zK%-8jHkeK5h_uI!ih8E&$dH$!%D9UMqyL;&nUub+;#OH-XH93fNaA3c!Hch}-rc;A zRH_?7aBQf*>1oR#SrV4Uri7|JMYr2p%9&v&=4p*CN4`w41PN&ehfZpj{~y$fSne#p z$|ECukF3a|d1LZSm$(2`@>td{%+!dnY!(#o=sU0&AUdKOu|yGSDhJ|GeVc~-#4 zv}1-r13U?;0-OG7VZ$X}q{=3wsi_=@LT;8Qc1^Dra(#OV<}| zoq3R4*+bbSubbxMpf~HZ`mNYkEPiSVZ2SC)0fVl_FtHeTLX+4XKR9J$IIYkeadoNh2&Wk}Uj(?ip3o;Ro^6=M(l zK}hz7Hf`kA?GlWqWnP5()c@9K0+>nbl!{jkv}rp1HO492DC+&yof1eZ$to4eiwD(7XvH8WWimO5wi@L-`mfWaQ{w zZ+D-`%C3rufl&rCzuqVee!=pDh@6_xwXubdiyI$CeS;y@QP)zDeIVy@LCbs-cDm`o zPwOBGDEs3%W|G}ms$Ox?=+@ffyJAi3{NSz+%RMvxfVp!QY3V5@^KTZ^SnrU8j*`GG z8RJ64<4R9Y@vJAmVBe-kJ3u@mh0>p8h6EMC0E^HDLK8lvjpLLNVi^YgW|<|!5XC*tu-U}H>a`^*}-gi(CTivCR%0wMQ7P*nj^#+&<;##1Ke{m5*yNf0;Ef){3c@If?fmSXXl!T_o7H!~|7C%v7n3UH#4Qdap972PJ z_8fA6BD#brDZ%$YKUzHpCwbxc967^)bViY0u&|JgHugAVEqZ2A@V)UM5Y|&WP0pEs z-7eunL)~ED0)Q=MrAR90rz$fj?bL?MVXKvQsi9L94141iOUbDHuX0 z&rD6n4SJ@CXO9wE;NbIl7S^8@!IC3$3Lti1oX#tR{-yo}w10W5Smg2Krf|b<@X7L1 zPtsed_QZMww<^@i$uL<6&vnKK*5a=XcyvQxD^#lB%|OD|t%wgLGfEVL>DDaYR$6ES zHyJq%wkbYvBCDO%N&U2h%g3|A$bVV*@c98R-po8)zSQ9^=e?5{w z5Y-tz;LzfSJXyJ9@7=P>5ih2E4;u4q=>^Sz{C#l)#YWR&wo9=WaMP~ufhCv8Xlz_j z-rf>#p(|qT`HlE{_g?<`KiEE)nkf4q+(8AA^DypSwRj%!=%`=BiUa4WV=cqi z%JwpGTeoIpJ%f(BF>$MwSh+9ayWg#)#rEfb6dFBsL*ry%vx)DjT>y;TdpWXGge5?& zzN2K0+(<>LZ|cDfYl?a=4{gs7ExHHa@g3##K2Xt>lq=wFgq9y%A)*W9G0ANnOqqGF zt6z&qWXzv<3V4X~(^u{dOw2=JAA73MQg8gy;gy0Jj#G7V{3#78efL?n={AVvd!nTP z^^N9MQ7bcE($s#)L^_&$9!*tQpJ6r7dgCo^9CS?X2Ed z^12R~~1kdGs$_*u4MFdxO1TsLekLX_@=r5>hlEND!#I7LNKoV}*R zkDvrY9_$>8sW6KqxR0559^M6g={K26N+sfDx`i=kgLUDYiAisqk|``*CKX2I6pK@n z6bk1~SH4&`uCD#a7)@yDag& zsU12x^(Pv{mI+;ZAvF_#cTB(0drk{Zb5HUs$QcoktE0brYZrW};_r8tusT_tOEPg- zsmCPAxeA2sXl*gmR+5Ap23I88%#bir;E!aqL+B`&YmW-NL|!?g45BjM6-yMbrZ$b# z4NA+?iuE`#dbTHTS+}C!T_rK;qZ(Q4&uG;Tdhe~$q`Z|ZG}owCacu6D9>q4+dUJ%}{yp>Brawg%cp4@s-Xl*)i&`)tg-q zc;q6I!U4r~(Jy$=%{j6aUyptOgwY*5tJt47`3}d<}|c;Pfitq{??UV zX*HPio$YW92sjQW%JvYln4;xs%|p^sL2N&RbyMq~xBw$Qz;C-evuQmYGY4)L%N)Sk zHWRWLpx+k1_)3}!n((>U&aOEYJZ7;JNpPDbi#-QKl>C9>MJa=E@F+Hj9cwMF8irIm zNh;U%n)+Z&WMV&=OtA&he;c;+fA90SXCGA*ea?A%DCemEC3Ivc^Ib}+M;ols;&{1P zQ|Lp}Syww#S9vz(c|GI4_GAH!ymX=(3v0y#F?H9XR4%uNXW@>x62@3yOgo&DZ67@C zw28PpEDC>u8Cla1N4eQPxoiZ^HY}`uFaNP@Mj3#KGXcVX9N+ z527|+RR!jdUQ?tVw=zX*h@g06-E*iWRJB|XSKxJrUT(!t7VhR;1i&o`MJ>MkBne0j zb}5pfK>}SDnIcwVq6J1dLdisnl3Q3G_Ly-|tSr92lVzI63-RI&b~ohua7$c1?_tTO zGVg21zf6SsMO!yFQaX(AGkJhcB;xPWpn+F&GgwVLBNGEn1&7Z=xS{gMM)M-%5P{*+ z#igt*Lm6LjTXu%Rv>6J!9PXH{5MBs=82(5*4Jmmk>JZlBOBs+}g)0k%Jk-1Zz$D*R zR~!8mY%me4fNMws;&0FjkphA;ysP;#{@l+C~{(|R%5lg~atr(GY zxU$p?8m+FWITQ7?5uV4^LW#`GPDx@_NLQBI+P1LV1E2_*z%GUjRfvqXn3fX z9XP>05zkdC*A;?wRNYGxVn^tqQ<$Qt8$rRgMCT~?_xrq#rck&QQr$4(aXHJkE2Ctd zYOCxH@p9~Lk%4Eey>wG3^~;06th8~O8zX77sdYO|6^i~pYMd^PVZaXAy^Z})!rG!( zc1T5(trl+&6SiZ64aanbc|)`i8}QalbsXE5J~l>}v1x_MNX-6jY_`yZl2Cj<3@$WC zj4WQ)V_^mkn9xFmyrLxzL9SQ;3*=>1q>z3ZHo`9~{yEq~PGop1Q20@(h$9IHypcTK zKM3h*;s}`D@sv~w;Fe15aI6~grw24uLT-ugZAAGbO)C)IXH!b`UkNgj?k@GDXC-8G zx5FBg=c7Y`4brPz==r(c?=M}~oFa{7J4L>BZ`P}*i3}BYzZS0FA}Pzc_$^ z%iFh>3>Q4w4?vUteohE6ejb{wbHhgY4%=Jw6-iWv0ysR*lf*(rIuJ`h+K@Q-;75fl z$IM;L@QmR#0{$kDC+=bPeH^;Z3P&M6SqR`50P4dc01z&xhK4AaIvmtPw=WJf;Lox- zi6^si38J5e%w_oTlPqRoD@jrpX}vnsJ24cpqDsJ_n;^vWo}ZxxJ9ra6G$Yq`T%ALi z&x`lLQv1xjH|iRi2U0AT&BK59A__n&hg+0HPncZ{a%!30PhfUrK~>ZiKF6XdX3;Xs zuBM?`u|jE`|4+#D-VuR&2vp+sL&7BI8zX0ZBhsG!*M|JB>B%Vqi}WcDX5W+fbLyK# z;)?}`@++lVGpdnC>nB=ksdI`qpV;`~WQ%{2-8KQKAn+%-kpyo2ghtJRbl11B`V-oq z4dk4np`=gRrK%f>T3#-WF(_Vm?Yid|A9q z{vm0nlMH#&T1`v?i|1OAkKAWGy1CC@8KRa#qs^)P_97W)@akg5S=?9h4x44-LB571v z9w;Ef6Np5HF-EC@A1_c+cztUVLvLo6(|zWUzrc~UxNf9>9p*|5Bb;ELE~+9lsbj4h zHrSrW7>i3FA`D>54)*ZX8;^`q)p-*RMS2MG6zawrXj(9cmeyX&5tQf4ta3U2IP<{4 zjTI+X-eYy7$eTh2g~lQb_UhFn46lw1(Pgj-u#=^RpGKmCK8D|yG5blQgytB|MH1fL(IfX0uKaL1lLC{aq1Xx`v44`t1v$fwb81Sq_LWE) z$)BD2qInBx46bQKdJp}!w(n}B z7yGw3NubLDEub>J>v=GH0D%R!^Mj}An8B`I5|-3+GtMhm)pux(undMI$cEjnp%T5= zX8`_7`n^#HZQTfL8GeRTljP72d|1u_Xw%Gfr-+jdNLwglSLGrPCE%~6B*7uQTte1h zHg=%8%`pI}3jSAM>h&o5-_)95DK0e*EH6hP^+ZPX&{2A0g~)f!Qwmuj=fY`h`}N z`DO(p>|H471K53hs zUHK~;-ZeZ|VJ6~E4idlC*nO`d9vbhu{YAVXKk%{HwzHl{#XPU`vYHJlZJHQ5&c6;` zl+;&LLxC$|WTn2#Mt_Bwwr-NFWAJjhR3abT3TC8JpKYRR8hf!FP z>y4w|V>TruT#{*_F)7Vh@KXxqLP;z7;2O;XQhGh+(rmf zP9}ze8ehiY=03oIiz!p{N=L#knuid|SFsvF8Rl|!(swSiV(%);885?)HLb;G#*;qZ zdBYDhOG%D(Is;P~LA+ZUUG#V>OAOz5ZA8EPPMQf!fww_%_pH&oa_>*b$yRoY#^+sO zJ;Q3VL;<>X;Zp~xdFc z5-$rwnsGyB(j?Mw(&xoR3q9XfWkmsz*OHg1doO>TU-RGXxiC3i3E(6cyeINBXzi31 zh*O$jT~?l1+}UR5Ov*XE8d*+3p5jT?<%9AIU58%VL3-kEIFqmh)0SG!zuE;IrgAwCRIjUy=sOcNqHT3ew_q`7Rh(D2nPq z$bGu%-=lR`tv>yTCx=QZWKDbuaSX7@vco&%V%B)<^9e?o3BU^|*A-krKJB#%2eOEX zfx|r)LcVugILasIk*ph#b1+<`%7oAfbd zlMXfdF1-$wUxaJtBS%5^1`9A&pW^h$7yXVP9_^cvp?NIj8?vyy_NkT3v7vQfN$s^@ z3|d{Y_F$jv8AQ$osADl7+G2vm=^_fzb11g#azRMfQky!?$INr^gmXgjMH5R@%GwgJ46z8mQ+;o`VzjwA=lPYJL~ zKR+A{W5d;`p)BPw0@KK!5jJMK69mR+g@z3%$Sua1%4Fkk<%rW~%YU(Ut=JV(+Vh{e zQfh8weRhhfreGa0f;ue2aE>&N3#GV~=7(k0eRhg|Kl_`}SU&P%`DW5VUN?m?r%nCI z#KLdcXu%kJKcTcbzyn?A6QbwI!qZuV4;Z8z6J^!`nXko$Xd+i{OXv3~MLu8hwoY&{#?Ec!bM2U-;T{&RydAv+=cZGoW zN$9buC{J3gli*1>x)KoXyFl#m>xCkPyNxTJ8wZ*DgLT2gd1WwkB%d`x7+UQU(@U}Q zOe(?S*1E&`UCSC$zqC7P&7|+3nl_d+-=%2`Z$2;G>Mc%$&7z|_Y^vOUZURHE<8ciAGJ>tk>(B`WAxMdfD%t2ng? zBR%pVsL2C5GA1(g+z9l7yajjgOu8xT3$v2VM3xt(;ov%{S;gbB_m~21VKbDf;A7P+ zR?)FUgXt*3=;Gu{vtSYcTZbL$K)=Ngw2Y8{&<6>%(;cWuq~Wuz5;DEakck*@J+JW6 z2!b|m4nwL0;HywQ?z4aL6p$}TMi9wA7Y3VcUviC-N1V3ahJD$M(K&X_b z_^R6Lq?oNgH(lBmi&jARiy#@B0<^U->?pE`q-@ z=`&A(BIKAbb=&9%V-h>Z-WQGiqpq|S)f$uA6p{w&>dou;oJR2H{R6~3)Q~%HG0_j) zv;N6#^k4_B{SmLz6vpr9wDn}CRC&z1A8|;nF_iF0 zeKlfk!Wz(S0P76_L#q~rs}pw$HA!(~>ianaQyy48RR1Hg5onn1Ps@^fZJ18!bmI2x&=g6~6_7(;q08mk6U=1Q} zY0T#Gl{2GUU4#q4dIo`$Kp{ah92Ob^rif8=E;S-H#vXryF2YoD$4Wq>m3CR#vTLRH zPEP7x+6%>bP~y6stjcZVC@&^Ta)UDvELRiIwQOhT(BM^2P?bZQ7*X*(M4M$?z9-tm z8)(6N*i5xHWaLSsmfOu%z87_wkcseb+jq)lAx^EvWczx{kQ_ci28z@C#?f{;93)*G zz=dHTX=LP>+VBb$orL>|gOVgXq)y^_F_4A|VTtL1d3o7UG&Zfb=7ns!*HldAg{R}9 zwXo!ZT4ev+>@m+CeZjEHo5U{#GF*jD5RL)~&BT>3(<;4u}PHt%mkK?BW8+ z=h};aGmPf-F~XOac|Z)zx7m|yG{j@4P2|iG6na12C&6^lA`0agwZN1}4-1s32{yKH z^DvUqC6dLa%ySSD&FqYk@wq+h$HYvE_SaLktvRs356@HrOf+DM*AB{ayY52cfG_5y zH@OO0c&c5tb@;}iRVXjIT`^@KKADV$Qq?ZRRzQ|YZTR;CeOqdW@bE1_!b!(lI&VPE zXws#y2c>5`KhLyn^sp#VY@mLJ$MrDM9+z4If_Uvm3I#wh0Vn{>`emxu=|hh8@;3U6R%7mF1-(M>kG%gB#qEX1xiKX(Z5)7*nGn7l z{8DEwFDEZUn0JpWqZNMUI2-$gmir6iSUw6tE#T#3m-hp}Hs+hwJgLIIW`uF|Q>dpY zV=wGZqWs8~iM~v4F_-96vMkX#(d|T7o*=S1Ezx}jmS0^ z31%W%BX*sVeg{v1i!`&9j-q?=|IR==Sj*6cQ)wsl08wXSYU=zPJRkBG@z+k0!3`cB z8x?aBn@bLFgX)k#4B6u<5?gewJandvtIt$ieM~Rxcf;E$>>C&$vESGh5;3e2NVL2V z(cu0h9$f2xSJ-Vw3*PmPIZFFbJtO8)+im;^=0pNNvXd<^Pb*Q(yj$R9?CXDoL*HaY zBP}RBFj)a5?u0|=U3tj7h(H-$l>UsoE??^Acp>N5;|vxZVLs)uk3tY(KzrVlT&v=@ zBGmU(7(mq&$j_2EoYAskF}_TRDG+C^fAKZxQCeof2i3Lf09XDHJr-A+LqA-xlJXp= z!ZUTY*dg}SQns!pPd{L7`O@{I-8S4df1Ai4>3e9T zFiemnz;9%2J%2*i3`1#)24}F55l$Tv^A~RD3-0Hs-OJ4y{*oV`YsjK9CmSRLRK3XN zXAO+TWa$F9Vtf`8dya!c^NMqF?TowQ9F|vFJm~w0R)7HSUacM zPqzJtmniFmYIC{llR~iTY=dlTt{rqZZ#DU=ugwIeqR1RF8!vWyX!f72=urHLaC>}Jj2C>D zkpKQ;X7UuQZrFF0hQ0D`4 zOy_ZEcA;3UDE2slw!s64hJQha{Tj);VdlL4t(6#l1tT?r3Ca&w&}OMBpNJ3(O2sU( zq5+(W2dzl1=WUB4_9rV=aP;0h-Ay6IA5yNqw+*ZAj~DpylRwcckdx&j(T)h^sxs;c zLD32Xr&N+#MlWzvZC=onB@9;-Em<3H7>bf#K}R7*blwt!0H0audk*X{8AyEWo0C!v z9wBSU)Dbj$NG=q?dib0(Y*B5nYD+&Q+qWff3OStj`Spk9#Hr`s#}q1anjfR@7ScC- zxzEU)7=VjR0z2R<8u6P#tQB+o4=N(J4M>v zA->$`paOQ@-|)$hU|(M=rN=eio;QG}<_|bKPzgAFqu0n$+2?lNSy}y$yxk^XP&tM5 zW|x#%@v z*Yr9+wDa70o|a5fcIJzn_U|-aTCh8gqMAXZ>%=amdxuI@+R@=XxvCp0C^Ak=o*Dh4 zw<%D3#jm-pSoS^ZuZsEN@z5OnW9gCEXilA^+{L@7ht^rLrunja)zN>E@#Pa(w0=1P z;<>G+g>H3cN|-lLhc~p&FZzq$Kt5ai@mS$rMC-v=_UqEAkx$_rqb4}cJ4m8frY)c< z?SgWYfh3Bd1w2M1U+8*?}>%V6&R8Z@3uiy(9 zda1F>tgvg-Ag z{mC03TBgOW=JQ+V*KLaf@Zvl|9Vy6|1hs#1_hOzf@(QO z^ne1=0rVawWI2o2IHpk1pP7H(ItMC%SGe>DiZeiDCLJcl&A$|B%W}B*RA0v;JBh82_J_J^}|G)!! zSLr}XS(8gilq9_%?=O;ltqCJ&>k!v&)i%(t2qqVPBnu1^yC|vYmS6T%U~7!+_R=+T zI~WU#GiJJn|7~4XC|71=qo6IYAH=QD94SVK(kg|Ae^zJgRt7Ix6RX~mi z_ljeid0geRHg_1Dg(nZh@dZun&&@zl=n5&mP)<8Rw$RQK0e3^L$}vUI&L-?>>n=2q zmw+5(vFM)(|Ag$^#*%yVXp-Hd+}Wh549VE$zjDe}{ulv+UrD*8>|XQ|PGinh9<|QQ zBO%PC_eck2Fh7_aBCS>~dcTZ~3NDt4hzLtmC`UD_s8gNezN<0y0CGR7mh=beG{#(X zyw)|QRekk`$o|CNYqit+)^yB06{U^XdFL55R^orRuU!+>Ki!L#KPlKr8KCuuaAphr zyy(#l4(X<5kU0HNt6Bm@c-5_wZ~mR0N(7iqwVj0WGQ>3A7w0lC}z zeO_|qC+|;9mtN^J>*<;ZVm*6@NPbv7CMya$H6$Aq8778Zj(tAnd#mVcZF9bRxmo9# zEH@>@)63ypILwYa&D)KlS#IfQy7se0cEhXD?8RObgq*(xKz$?%l|)(U!oQm{HNc8P zq2|2IUGhTG!h(y8kbln4sjSVyMKlhLp47__;C(oeovnlxR|zF8{?3MDY>p=}9bfqF z|E8Y4Rc&sn2){YBlzl<7&I~VjQcDt5#`$)>HAQUh67^69w6R1{tHC6>1tNyPy1?>M zt&sM1a=_yKjZ@CXHV+EqBP#~bA5x%;$N9{WuK3RvIz~GLqRsZm_VCI5cF#|3Q>1$j z!pm!wQan3tClwWM7UUp>^hG{qg*?+p2x0!3@z9JsDa=?33&)Snk?%tAMqdAay~lYX z*ulQL*>gm3^C9xAdg3jxjNcZ-hL*^X1LC8?8tlR@Wf9FbWheK;^0{O0sj0BBCdSFC zj|A#w1s?WnsWmsu!rvg@fTle!BeQR(1R|(4e5o-{)E!{HS}*0Yo}Z-zP_?)i%jYmD zH55G*sS~%9>OQyG|*0laQnC1W~~ZqVZL>rA>95fwzR zMMKsW*!AW#aoRKg^Z$)Bx$+OuFImR6Yx+VT)x{N~=um^Ep4h^3;qEyb(ZCRYG!eIX;NvwX|10j!Y(M*<$W-my>E!}u(SkhejNTNZIDng#EDso2qmh?pGrs2gfViV%XIkA^UK8;2 znW7*#Xc@H%_4X~NpxKl?dO9^fuui3uxYnys6KC{xlnl&AFqO6hRI5t`IlomhB6@&d z7$!ZQ7#W{`#P^$QjNkWg&e1f){hF7vHGm;4$O$AF&HTN&OW_PVlWuqWS^3B$s?^!g zUHulF&RsF#vWdPX)F~#ZowB7&+&vPUPb{54W#Muvq@=>)cPd4e_UlY<@ExucP*p-h zR1`)}myqI$T8FkZ;ZE!kNw{2`HF}=d0mFw+Bzseh+Qx`yRyegzN(|YXg}iD8heh(L z6N@M2m(sR4KT0#bYObd4L|DA+Y`AIQPq--Sb@6B49FBcc&^Dh8Sd_dEw>PEZsD3dD zVAvTbYQ_}rS?xuIgZ#xj*e}QJU}2>ChmXXdq}m?&jfZWzfxbgqK42_L|AFQx{g$$I z$2YNrS+V{)HrO-yWaLnI6WxB5E8=V{3sRNLUvor42XZ6-8GKmodXfxh1QvN>H>Ufl zMf|8m%)AUqVx8UV!aIi~gX+oAM7Yb;St2e{;j90q6@)K5LXmt$&r;rY4W0eu{Gxh{ z9#E<027q5BQ|VOz?Pk@g5wGo{KbN(0mBN@^@slx3k)NI2$<({Ru5s8>a+YRYKj;O0 zTUzT9vZe(+K~RY!i)Wv+k=vSX%w1Pw*(SrQ^P$2)Yc1pADRU>c3MCE4Qd{3W5101k zP^?AUZ?azRcW+nl{pFIfq77$`#)woa;l zJedY^wyY7B&Q0j#2=PW~y>z%tao7twvV0rH#+B~7MbDNw7WainNE57aq}X2!G6muo z3}pU~D*J8-hyuMjJKN+o?H4PI)+o8{6!!*{Bg5t!Im#n8RoeR1B}RVh(7_Di&pl(E z_3xBk*)pCN-ICC1cCG2*@aPC#1^KtejqR&mK8OPupeA$|4E86}SrK}`$Oyn;Ftw7( zW>Zd=^>|iiNw#RremuO-hwH05e1UIABh-A}G)O}O;%6{Q(k|h=dYFpNn;FZ8(J})f z8PzP50pU~z_>AxI68q0J3!Q}QDO3^`zVvw{b@OR8c!tBrMbkXIKcqaBR~C7GUQhIV z>4G0>(PJr#NHRuBOYQ+L`hB?BFQouD#Itcui%)w%HX0?L5aY%rf}ZrSE}isgoJ86E zsNz)O@)XJR;CI}AGpYL!TGB#Q?{ItLca`n_nNv&NCatAyJpv@IH_GkEByKhsuB>;Q zS=g+#O$NvWS3OwQRy>h&)I?n~Q!=ml zo{juMS@7G-yaF-8nZTcFU=rG7Jr_){0bvf2!E;N4*DD63$hFCqDkZ#dIy;xZX#stU zwSomAB&47}C_ShS7?PLOtYf*+7VTfy8_FRiVTNaVepC4Uf<0oVNOfnNH}eG@cfnY= zaac}x%T&GD26u+G(7rPnOl2qzOi28CjkXNgsPnlC*B6W&5KzB+gW#@qJSrSzzPlr+ zrM$v?E#M3xBvD^A|AiOw7eNay;!_?FOzuNS`obf4i6%`7{6iH%9UN5|x z9NBd7_H(Gu<_{>QjsMo(B@naGn_@I{4BjmSlCbkc)oTeAabDEm@yk1(=g`4m|<`u2@K&u{v}=n*~;}GC4gh3m2Z+ zW5Sy_BzZLhj?y+3#DS4a3sNo?=FS29Dg_e-(mrzSI8Y9uvmg#m1UBwp8E^g+9>Xrw zpXC{uubIVdCf$hnGEh23n`(5*wBy&?3RgyFj1S>3*7X6?DCkK z5(BTqQ;K%Z-vRCPA0(04?q12IQq)e5vl}Gumf9&WHGe*$G~nnP`x;{)d6CLHGlOP; z^(?Omde|p9di01Chr>?FRr`oR{&Ul-lWoS&`(GIve$I1QjW9DihqCPVLm~RXtjyvu zKs4YaWGDftTcnNE=Z<`MlC2Xk5xMkhj~45K#|0yWh(B(d z$M@ws?xifcfse&??qLt3i@{P1Y-^$ znCqj&&(XgmM}w3$Y33nL^o+R*Fv8>0eZwckBuN>!w}Et)m#lZgM{wMstGasSh)MfM zG`Dbo)uAL`GB)*QA7ytD%@T8%7Ub26aPFGjtI)sjYv_uE>x$(l4Au4ra!RmF-CeZ5 z(VR+QK=~_Ir&whg@N<~7y!7@fC*4p$Jy-;l9;M1OQH{r( zWqDDs=mw%_t4b(q*-5COm7>D!$u+4ef-Ec9or{hU!~q&*X|vNNOeAGOI?49Ou+2j7 zPql9CzhaVMKY;C$xrga|?5&O2D1zKkhnzAqx?x$1U}84HXnfime{9jWqBfqd>AuJ` zv-ssa06sv$zkozFFg0DZ)m@8Kuz*BZ-ip56shN?`q##3R1UZKwIgOVgp;qlA5SnVW zTB!YItHLj^S4=gF`65nVjiT`re7&Jo%$hJ%O*aF|X%7UMs-rj-1lJ;;cqY2uhJ8;E zo}sA>2}D7ou0YG{Enz7M-U+F%)|np~#Pr~}buXqP=bhiUUy2uzFG`at5LX)c;^K;3 z@l2a85@Jr}ZaWGWl>K8t? z=i`^|^WBsWkJHe+d8dCi#H{kxWh5UG#%Tq<{k1<8H20g2^usWonm6C<&qvZsIX3-U zUwWU{F15_Ob*Gfi0l$dl4x*B&=Jz@7U!>Qrg9z=C7VDjg;epMl8VzkP(o?o%YeCx! zc4O$HTzF>i`(YlYPIRNu{aAutmk&Z`CAltC;)9=LI+sTIMDB+dqKe8BrQ%d#*m6F7 zvnQGl`q$5?LuUHYY=YWz@Qt)Mg!5;z4j$2N3#|;q(maw~EXCvv&TysJ*Fm=O{Wq|& z&6vk$+=63%^YE9k5%q-Yp!#n4^teny-v`&7ojE#9p18m@qqKC%e)qV$oOVE{GFS83 z&jp9IWk95t`Qwv#+oNGZDzwSF048_6$tFHH7b0o;+PR2B>YFX)zKx~~*Pw&p%9;2` zIInej$IqQOF(Y7IE&OwFj6zh;doG6_gCfV*$$WtcOwyoYIyZwZ ziO*DVy9My?Tfg}+za}kd8=>`msaMk>)Hj^0)rbdF(lg_vBpGZ3JsMfxUxtH^Emx=Y zq`$rd1F8by-r|BY@JB66e#2Ts|BI7I#oPECDnrIy>vA)^u^{Bt<)j;)g>u^0fc%j~ zN8c`qM1mZYQqC7BJVD7iU{K_z?fB5{2Mzf05v+A9INR}o&&4)eTS!q-wykmVaOY1!y?~+j-z_fR^4VlHrq5EUt0LPOdE)&FFe(ng;fD_}wwVE$0fXIsfGjaR*2nNa1|@v4t>O~Pu%5!d_YIob|( zf=Ry$egbkX5?`W)mq#OB`M^Y^6TChlC8k4mRa+HLo%bN8rT9zNR|y!U)plGM<$;H7YA5pT-^!5wm7Y^lp>!BdE7{QS z^}koI=?1nrS)cX(tsDLT^|)TetvHS<$&V6peede(1mz@}B7R2;Ll+lAq-=svPDv~y z@~}xQBL|DkKD3?Ff2fnBdkR0n7@L;E0-93h>kr1ircFx?fQRNW5CMfS-gbk~*kyOK9cE#SAm}9&p9*Kx zIR2_k>WV_{kx2p-=`PS3Lyx{k($?qv2e3os#kJO?Ko^M&NYB;Jl_iaG`5{%GFZDxt z8I=?IIOEle^>|Sg1nf*=e^a1&(^I3<(dbX?n~~fd;Mxi5tJgg8jI9}ED;*XLAw^L*{A}gVULxu5i4}MFX_1sA>UmVOM z4S)+TVuAz|9$922FKgm>2E_MrF|ml+@+?OXZ$|TGHLP1N5Mom(7D?p&QA_7547U+z?`s@4?TgQ+JP zAmxb*c2jSHw~chc<;CHQc%y;fCDIm_lwB3c%X67J?pPUW$!`;rp5G5hz3Mb}#%z4D z!@*S7ZS4n|=k=Q4QMr#?8u*6gueb%Qq*3;Zcx55yUq!lq7N zH1>xGK|bd!C<#DL?AyOmNzup`y-#;&C>*im*g}5SZFool15u|`)aUCdpJkaP- z(4~-7e2d`T0~0^oeeci;otpE&JC+(Qnb9ik|8z%3z(7(GO;tdvRhB&kTD^!K1u!5M zWq7h2rq_FOnU=OZhahS3Zo$f;OfFNrQw5#)e{>Pfe%BZ)#uMTaA+2Y)K#E_!76=$g zpYYWh1E`tw6>y`mu2`4$+r(8NT~iVk*@)Wf>X@G5A-8K>)?19}&8;3k3HO(H|Dha; z0*MIuj&4EU@~KKH7O9}C-H;VnIWrR4yVnIZ&DUGGX5C{j19}ih=5CcUiaRT9X;iY$ zFU{f*TEiSwGRUAE?g+C_Z%icbqAHhhE9cn6E{XvkFG^FDFR$?iB5f2=MVJrfuy~{; z-)hz2%F#o>nRiL3SQ+iJ^=`h@2LkDv<<}myeyHrNd;-G^x+8aOXQIf82{YlS8k}3f+;ajpFh+0Vd2;! ze_v8?u}8@pK78`s`AB>m;gfGu|7&wneO<+K8s*P_g;(%G5l5XlD!6es7Ki~f5wJ}! zP0yJ6_N9+PSQ`E1j}yZT@oTQQtjvog9wJBr-uxPEY`OYlg3?ZU7fc=N0@`?KYW(Bl7+DVnI5Dc5Gmz@QjuB|PPT;_uUL%Y*#H%BfV~uxhf-cS-(2T3y^R zNP-z0djF8R*X+3+o8N!GfTt*Suof$EHcVW@P9DmeQVCYb$&+{vy-3q<1qG}Nr6|U2 zVr~45Q5%z{jxno(_4h zJn#H~cXB_g8;&y{)XX$PB@(+-I{L!{>+|05ywXD|iBQYj-Ot*%N|Tr9*pBNISke-W zRs0y$sGyX!@)ial=W8urr@9V>8&NY2xBeiq5VJ^0(MvFa%ri0;Gw=uc-6IEK6!0kH zjM!z1fSwVVY*QQQ0&rz}!T1=mL#*w!Md>Vg4OR3>EUU$}HALb$Y(3`5+J>Gl{fkyO(I z<4t0`_!{b#n$`hYaR!=*1t7iftfHyJ+(s-UN)nBNbeL0;~ z5W8wj3rkvDm16x$igZETbg**C?Lqmg3rnXFI{<1;_Zl(ozyv%5bQ5hq_0c0?wZdV{ z`_bRfrM_+HUw$+@t)BX#(E}~Z?bXq>?ENWN9>Z`s(<2}2gsSHhi^0p)4d{vXIEf0H zpWYH~t$GZ&wKlUBK3&6x6;Jz(0G3p5p1lfd^XjFmXxfpTpqqz6!fZf@yqfgw)gZ8#3?IL-JYz0mtpPm7l(^-dD&$&*toWwm+uGtv5z*~D7-sy0`Lcme%EYCKcZ z4Mk<`=cd_7fn^^sD4&|{lutC{dIt}-x7?uzhAiCs{m{3+HbrdlL-X9Er#@Nv zbk2*mEx-Mq&`DjmbdFi++aG8pHz#QLefQEe$3koon8ZnZYYeQ-h7JGyR`s?^VSW84 zOH0mWHh7xJ06dph)v~G@a%o0<+dzT)vTcb%^D3<2l2ZO>`}?^O0{%EN@ALRcg32Si zN)XAJ4Lpa9bENI9w9Q-ko5>$R`9iQn@nSe~m>)*p$C$?a?uC0#2%0AQI4|F5Ii78X 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

|3I_kA=#(=rfP>)#I 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Ǿ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

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 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�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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 zKl3WYj$q@a0(;wBKE1f0U(R6(WI{D5q;uj8LZMv)U7*mpCy*3%!B#@I>l0_gXf2w@z@yo*Yf zULOiB3WIuUB5B}(;_uVjC*R!-MIS|z%8ON?ot2HCy-~~1;~Pa`1&()*v(^afZSzy* z5th5OXuj}_J%%h#2HE0bTQWVnwY1o$LuH&!ec(A~n~xz5b&7?h{~A?u4Q&W0&7Ca0 zmgqTesp{DcVCU5V| z_Nm=meQJ@7U1`W6!oRy=q=u84LY4<=!?xQmDtc`*bKbIfi-A~xCcJnCrwYSLolngL z^?w)!>RF=Pw)^)U3`v4msuP94I{EPaU5VA&B}evH#7W>UR5oWrpCVs5s7)_6J-3_! z9GL`R8l(3IPc2n{l5@Mv?ULM=ey_&(A8o#eSrJ#M0Av{5X7iN+o~sM!Ep@#t)Ogb? z$DNWYHeatb`%=lJqDOWe2!Az)Ps_ia4&n)hF#+3LWm)$l>mD^L@5s4Pj_Jf)sQNgk@HEqRslZl z5%*>|hSDXm8~xQjNI4%3pvKJCOl4|oG$rpIWn!K{^ zc^opp`jIO@6kWdemVef8ug^mmHJH)|w0bM8r>-}kP^he`|5Cb3KLj)WxZ%>~Ncg&R z8=Kg5ag`Jjb24usMS~@lGS=AU(%8Qe*g>wahpN(*SkTVy3ILtZ7MP@aPS~qSwTIQtI+BDoRU2D|MOiCYiTd=#s&l8NytY=8+FZBI{Rj>-Rq9ns zc|PKqkroMX*1u=H%3Nd;54`^$$W~-T0rBc^Z)ig;i1u%DEp8eC<}KE(7+(V4>f+{ zqc(jigE7}o>MO-+r2xGOY9rU3aUfx2H&-TOXK?%$K39p@d67T=Senz3^6zumH%PLr z8h-y$_5z+;nT=1RQ`kpgMK2?f`+_9YPPV^%)m1?OD+5!3R~+9LP`3LUC><9qU+tCQ zVAtEaCV;l3m`zanS8Hy$-e}fM(ZC#*jvv)m(Y=lWUY096OTkZFC?z5{x->YR!sn-K zZ`8U26`_;gZvv zgu4BCis;C)h(svp>_n_qm3($qS-MWV-1XDAzY(VX9A2vHAxKa}EOW)rD%3Yg8D0hq z^wx9oqE%7sc~9j7)-Ws@$%+mJ?+mH#J8jt8@^))xA~R?j`{~AZtIXO@v__`j4Z1I& zZ!N@$Ky(5Prj*

    M|Q3QAsr*9e?T*B9j!?zM(0P_j3?g1`l|WO2d2{Q z9&9M)e;m(=gc$?ImLY7N>Y%@iq9AZ>CeQD6KAXx8-`5JpO77t&pvALgjO9|DJU?^l zUCb3!X;k`7!c)xjZ$Xku%D5{Xdk(HZTxnz+cg{rcW;qKv(aH(Z_sCT=Ag|35PypZk z4U8AmsJ$gi?)@vwrQ_4GV2o%Vlp|RdjLRymEbkk8f8q>P{}-kKvu7C z2x>(biAG^9cnKy`FW2IL`%p0#W0uhCq+D+jWwcGX~cdhJLrDI4d;@SjijC=+psIl79Mxe#(|N+;lLf$M(X88TTk3n>cpCk} zC!pP5qQg@2^3Kx^H8C>627zfZx!&1QHwk zq%t;#MTG*FJY5*i8COM!{wj)RbUnQ4IJcriY#aXhTI{C{k@#b3ZDvr32gdOMt7!ue zr*a*U-6a~WObGe98%Kb{P_O6ipckHW8}b-W*Ee|WO*`xqih`zwGOA;)22GB~!>egi|7*8q~6m+5$e;8D~C8%{+J^VDQdic_!J`}pr7hT^Rvcj!wz zwEe*^w(Q7!5PF1`Z~*9@q+jNLi8=m`Mt~#jh>_jM-cpRW{c0}WV1vd}Xf@ydL{C+| zWukLHuQ)J}j2(2~gYHFVY#oji-{m(SjCeZ5bkCs9@f>CZQ!aG{d`#~lC>aY!4HHgx zg9(fZWL>t=B_5g}AD5!N%oh-JFE9T<&F>kb#vo*$!dkVqwnZD0TCv-n>fr zUn+)9+Wf2z1p&J}Sph1WG6@*P?Zs|c|mU< zj6sJa5}%YCM-_Tf&DekmDKN>ySUBdJv^IT_2MIH%&IFZ09$))9Kc2!v$(V`w6Id!c z@Kw$pa!k{Cq6mt-ojytVALEqvxLV@c>4&oHq2+m&eA7-IY-3E`HHH+LZnf%YVJjDl zUUT0!%S;=ywk0lGrnmrC{+Uw}%!j-!V!mGtJq%cmx`;zDG9{Xkr_=Rp126(9N*>*M zjsowhWQrK(n+kGEXs>j6&g_T|JH^C*WgZpdzD~^0X-4$8VE~TgP!wtPj`|(&n5<0P zc-*%!VXG_Xz8?c>w{A2R6MB`Nk$=Ion)ZZbiz{HiY%yoqE8a-}3Jb;OLd&n2k3`FxetMYbS)C_8^cxQvF(ffq}=MzZXG-s4FF8*xa z1Xh`PIcmVAYYPqz5ABIDf2@mino%JcAP=FICq_q1ik8D=GtH2MeVF(u30yiOYLncm ztszlfnMZ~^l8D-L=I9ol)q}kWZ#@gyyt%vJT{QFJuw}5o;BMmH*u%=xuKsnDB}rV@ zB(JqJ_bqe{vC>|pR2m4jaMVubE$dmNwijP7#yAvREh(Wk;q77V7kpm=T{e9`o}KiS z0ctM>mx|IEHwy|XCvh@`rz&5u1~!}14mB$I{$}nhGYh6^4Xl1$UpKwEm^=Z-w48i+ z7;9q$R~QmCvyqQT?Geg}GHf*R2c!vTtx_(0xmI(YiM$1-HPCm(ElA8!V)}TUO%;wh zvsT{tO%RA*<RER2wM{A*HNT z>`QKSV9f3&SevhDmD!iw*yF8JrhD#5SMp}r(Pm0!NdkyfD-W$k@a3pTw1L{owl#zi zuc~MUL!6MN=v^XN=XV5csGB`ZP{9OR*O0J23(E>{2HN*s8uJGhI8jL5Nx)4h;CHar zP0)pCq7D($j6Ak7KkOLA&EV~b{5cp!NSc8N?i>BJo$KWCu8?&d1#AVlrf&U~4)nA2 zKom=k813hNnjPHsU$i3^w74`Le;7{KbfuqQmKxV)Qr@aD@bh*eQ$|bBbR9j^R3;)j z?5!MZuo-dSTa-+N@T4KkO6}_AeIXHE+FI0JRt>A6mOSWk{?~)~k4}rcI#F)G;6Fwi zgjBH9&`3~dkY(B+8gN4_%TB?GRM?#?1bV4ZQdkc;L^c^Ty!US2qu>Uj7BE$bulMSB zrROKir*{WN&tV;}{O5b0YhV%Onx1t5`UC<$#n|roM&`=sDroyVC8GruR;wcoVf3ZO zHaI4`oyi2FuSAOChRA6H;P?yWtB0mC06EGnVNP-_`r0P2jw7F=n6z=^I}Ludl_pBk z)jg--WU{B5r3-6T@;g=-U&sq%_SBzU>P|++Y=KM@R6rvpq};lhY&umhe%ak+E7z0b z+tO0o?hL2q#wmRxNsqHaK*HgGJ~TWU9|y(sje^H*)C~5XP4!zcnL#Ub02Wst+KF8B z;RKBYkRzSn>_6KoxvKbFwKal8=>a#;{= zkS6=0Y{yWxqHQGUYS>8)9DTufX)lDlQ)u^QlT2uyct8ODxF7`1)|Y0X!xMud&RSL? z&PLI4>;!tr3LLX=zZK}fr0=h~gvf0#@(pSEH z$Nfd}8NLdy19SStk8g*^@00E(Ca3MqeSe^XCmCbA za-->Dkg7G*H9v@eGB`S0G}}9L#!$>u;LhlB?`c>tRV2Du!x=6hfb8io5PYhJm@gVAAv3puJLREZiExKuStrEgFjuW2 zdDImr@zq%lh*V&GrUk^tiw*+`4h=>IA1iMrR=INgWGr`Ls!V^`N3f_BABR4gd?xnA z;je(hzK%yW#d@j9SUDZ>+76sqd>@e}jpNf7f_LcPkKA`Cn-nJ^TYw#DSRv@3f^Om2 z6E1R5tDR$+qoppHMZAbJN*#-mRu$4vm)j!l(<@Qpxl!wa(!^U+<|#+J+0L??#q<)r zCb3lIl`3a7AAoeBZ{Vqrz6Jyn1H`FOmw}s=#G56`CoRpNkRnuFT~_K1qGm&h(czaP zS_o0Z>3pb<@Dl;t6>PBm9dVT>pKCG(cmYSbPqg64@K&ppIcY|0nS2mJT`EphQ*Xj+ zSylb~Sc=Ch>EX{^Ku;Dixp7|1rtM@Oc0kjOa;r}s97BmJ0^7w6LJ2u z?+P~G4L2^j;2p{Vg4kFP34+icUA4UER5PJb_u8Q3UParV`n8#?to^e#wz0hhDAV>G~=0g;?a`)*8I5A$QdIWR6PF-IIHw9o@0z<*vX7_bTH0 zrKq^k0a9&c3>fvzpoz0h)j7ycg;em+`?x7%mNp{en9n0cmPp$tM^#=VbKCoOvx>Nh zU7s_XNd-hP*xqfgz23`;fKWnnj?Sqj5s+oL^*&d>J&c=(u*6^dtF?V2TiZVVUJz<2 zbrkEQy$~LRZYZ5ZYqJ z_>mdlR+|qMx2#!E%6n6OprZL62uI$&BgWCv|sb9kkEZytVPrxWjF_l%~}eSRlV!vmH_}am|%-_XIM{ zE9Jf@B|pX%6yyzm2S7i#ofKV|ymJrO6O+p;BzCbaTPriWhsyNMwcwFm9w!hZO%xp7 zdfg0EjF`SVN?X0I!6{7{<8toDEo@>WG*yHqSKbJ)vDBI z-U1r(-`gIgtaDFS`M6P9es838Mv1taeo%egbnWUtCVJqN);bKG#(AXi6!h_KXC!38 zBqRY3gVOK$w5|wPnLqTdbqNw!!&rj$!2r1%md=;Q0xrjpVnH|$hKkO|Hs5fpr^ULp zK!;^kY?s$e&o^;SbIVz}PSO5NXOtCTJ>w5+1nE8AxU$;=S~*^=mX^*W1|3rjHq>yk}u*gO9Bz_Ifxt zMIBPo_*K2$5cTPF*uK4>D|VznIo{c+y3tf=^vN!wSu;VRlS#Sg`<&VYn=DjbsYeZ- z9L4VNOc!Sb1MdY-A{)@KS!vuwVYC`>chspI3H4Ir_0PNa#0}?3`{ebkN6Vn&)u7G- zZhoaY8e_6zr+0A_cYj6t^|o@40?a4mW`jwTNE`EmK*Y{d0Fm0AU-zTeG6A{iPOl^- zk(53hVGR#1zu&|}M`>L;ZM2+gU}-x{Bs7qi%v-E}-iv6jJ$U3dl!ThIr)G0>GYQc- zWA-9|6NAuq!&AsfA!3G4*ZKJ!(clV9WDJts#zgsrmg-&F5KQ>ds(5Vi9+JCx8ct+V zJmszi5fq73ULZD2d86kQi|Of|uY8TlkmJ`zyQEI((cBjnxo`DM8WGln6T2#?PIx=8{qBv-QdT-_4WufFk89;ET*FTyTl%VfoDYL6<&F3OPr>jNhl z4BTNRZ90%_Wdgb^CK-#lGH5XwYne_#{OjuF-**@pofKn8Nz+N$8O>UFmbO@wPP2p_ ztqX2$e#D`q3=ilfHV+y(v9*4z$4E*q8%jD%0U(5o_5$M@Z4WjVYy|iE+7+c>W7-eW z@-ry%5|TA@5!llsZEg{okD_;ll-Q_txW_!oe`PL+QH?lf#IZ%6O z@q3TxN2>^c;2a9!2&bMPPp_>^8krpVkvU&`Ulks!Y?)O9=9E>m91V!Fv4U#lQvQyt zLEk2msWUh4k47-{;V{c{c%4jB9@Wyq`~Zk2+1F&$X+lag@^xVca&fP93$6In_OJN? zXa__cscFwxjp18Be0MhPQA=)mr>_buZ3R&b*)yTnTG=^8y|-;MUuR^f4k7NLH+-o+ z%a*ce4rPsbzMVK#*=3uh&Sn8|X}%__Jj5zGoI}}GXWph`nmLwiP#Y#oQY87IVEADNF-yc4OYs4!l%4?)N3uCmE2__)7H}!@GS=` zDs54~g(0;EFw06lVY8WEO){QXn}oPxH&0Jsl)p1JCjnMG);bV&{Jl%Wa0L|JM@k`k zf{4SElEK@;4yB&=yT`xebYr*V{*tgoDaU`pFSJC{hx%lVXvw@8=W+C*f`s)D5Cl{{ zXR?4zw%&0BZIg`;ehFc&x#Z;2Ww4o@Venf z<=G=kZM^hReNbU`2(rtd(;OR2gvq|oMw#H8zmX*ZA|X@fk_>ipJGy*tMjh7H(h_~H za5H5H7bL0;=?gq^@~aK9-ai?t>=6Y&s>ZHH(?PMQy4muQiO>j3bCW>UlFj8qh(5y? z=M_gbHteA#cr#1u{-s>+n#!rgqNSqa#A)UXMAjwOW?Olb4~XLQD3u^L{F2VV%eiR; zX_L;LAiIAEKHtm=!xv>C6!_^kJNz{>7e!m zqP!UMltH~hYRhSoSRBk)zFfqT$k^h_8AG-dBP}_A$xjgA{DPYv`Dq}de_{epyHNpu zZhYw$!Nv?L;MHwFmY{czTlL?c(*R5@#BwwG^nI0OGkQRIk0p%DJv|_pHwc!!ad`Ce zLU&VW;XNu<&52(qOGxJEoCWo7{wOORq+k3x+l~ESYblV_-Xz-nYh$(h?E(^tYJIM48vGtb3)Q1w|H( zBBduwH!>v?izDw z@AsVXnN5C|ZU#3^1d!}(!c=>|LE}qz5UI#{E0mn4uLQad&VkJB`dh&H73e(aRY8=; zd}xuY$muaxmJ$<#Nj*pel{MwXDAPGxD87|#^Ko;uu-Pfz>}x4R+WbaAXJuIY`ddlV z#M~2eQD_T%B_QA|uaa2O8xisazEo_C;9#|s;;it~I%4H@;JlUWf@y@90mG>X5#!Xs zfO)V@#)poBYyp5#G)!===RtL$a->&=;sG0#totz{<UE~S>z z(ct6}^d`bu^YUB3@Ez^P6&nQJ91;x+kS{6rR&cXTVFhk;!6Nha(T=U*7 zVaTQqnK^9I*!dQp890UsCi61Z}1obHXkdzR>04>TjM8F3O8Ij7Jk$73yg z>mglA4?wBYlf>^H$pH1|X6K80_2-=FcAq=9KSzM=nBXFH0kxN|VE#O}4*BGYsS%ax z682iI?na@hz*Q3{jZihmi_%>A{9{+=>j5rY^dbkJQF61cUUA2UY18OBV9K4(-K2T< zxjD06d5`N0_0l(lzjI6U%~=^nbv8A6Wlbf+b}!h&Iee0~GX@QteXju1)+$QjMeVsH zYQ#BSxSl1zMJnM}50dMK%zK_%O-1K)i;7yhs)N5$|8NEPTS)jM zn;Gj51?cp~Jb+G#ctZAdNBl_A-R)AuE-$th=tOllvYdNAz6freeX_Ntd`##?dI>g( zvUmm|I3@ypBri&)M(jtsUJK#;;#D{sfQ&KZF&~>0H%Md|Af)WUiU-H)Hr5>v*))>< zt)`gHZd5XP>0YODm{G2pZXA-|B0M-|&QcbGs_Q|}R$w>~1l;VoyPb|0!GZ)6sQld{~zjjW{ukr>^CQqYL8 z3}_;LwtztwUg~IoLdLU^V^+B4(nZVODJ}33lwh4PjpFsWq|fXEEk!f8-t%@;Tyd#0Z)p#bFeIt& z@1m85foKa}CjzoP9!uJAE@W5smJ)NGQq@+}SvR|)MC8C&x+;fzQ{cufF{U~s5hsg+ z*tYwY(5~Y)ax?eGLI`@_)oa!Zw;FwaRK4!3#kPF$hZcr*cIJ=f(qXd#p;s+nvXsAf zSTNJ`-BaXwzMTnE!3xQL+6KwvBk`T=;=u9&C!qT(LB%?;w__Y40w>Sd(}o&4D{-2) z@_4fsofT!N`)YRU)Rn`$w4Zu|WJkvI6LZk~@C4xxY^Bb0*<+(zJir+xO}C<6163a` zQ^Ec@{uPSmwQrkmyT0d8E~fXnxRT{jYJgiQpGQS_hN9^(aly+ZV%3Fc20PO?+~LS& z$#@-`9cS7;v_pR*wx$2Uj9ZT1`_4MaX%-l*=;Mv{zG8M|L_MtSn zP`r^ejK$54!Pa7?n9lkh?S_wK15_n60PXm&XV=%)C4m=9&f!fOB>s1;NG{ph&mGyaE#>yxV`f8oK{et z=Ttqpc6VcBE+(~_B!%I=FO=pUn&YKZE23KQGrGIgRCtpDhs$?wSeHU=b$n$;ZF^II z&GFi{l|wp_mM*EM)r+!MJ%Ki_quk(v;YUYAc)llPXE*P^37L@@?yu+33rF)ya2ZWP z6+jDmJ``h}`rdJX}09;M;qzZ!w15nLjq4BrToOxRL-{B2VKG<-z( zIy&j>nRcc8L%r!3@3b{CEv}5Z6KD`QG5=i^x~LXvoF9rPWd`X79NShCFK(y=F%_r< z;Bc%2FSYCOI&r@V}Sd;4Hz0n<)aAz_m<5AGJr+zF50WNh)^5 zg3Xzr05#x_c4dcY=~f5QbRgYQZ@~Xi{uAQCR5xQ!)@{U>KIa;TR}6tiZAuJVR$Fjh zMLZXXi*z{>LkmrX25qX?Jj!ZqWux{Kj7R$KQXpR2=xS^u2fn~|2aeti=7`vFsk9vx zv^ou2$^zCoWo;HA-!{Q8n|%kJuEoGg#2(A?dUZo!P~tkHa*Ic4R+@OpP$y*J=2-RE zs6L0e`J|NuI5^kNv?r+jffSjP(b3xSvTQ9pYXbJFGBDboiLixpXd{1KhH z!+_tB-pbb(cD?K@y%yPH;Igc3Tu;9Q#myXn+qU^&Wl`EMMryJPgNRCeN+G&RuVmJd zQ8+i}{N6EHk`n$9PUJnWUCwR_RxRUPicuIYdza~i*C53?(PO5nyFT^+u^X|J85V>P z#I@8Q?|r#-jRI(=sWxedNrLoMDGO7#v#zlKuxxXW+wX8u7BFJ#&qyk~D;MW<9x znbNfvw+5w^7L3ysRDR`9wEz@D|wpvOax5_L_T#npH&L#EE_h0sP0a^ z#dG6PETgR6o6Wqi@cWk{i2w9h3#glWqghgz;q)wYG zE@m5)wKXB?)+;MWoz=pO8b3IUHInv(_@c1h{9|{{fM|~uT#eO+DAoqh7|6M&nL1P#J0JA1U$IW2|7U7cG+x$yn z9y+Y_z?Q%*3{u~X=5MAcF-*H&BkHTP=bt&;`Bs{w3S+2p(h*K9`tjPhYH>}$RQXZP=*9{6 zB-*J2@WKS@Tzq>}fp3}9&v8`Sn`5hdIkQNGide2IP<30_cQK;lnPz|gj(_txUBJrP zDhT!8KUfuvTsD`Q^_&hr==%kIuc>Jn?spomhkhIeUO{M)h_}N%i8<=bwE#Z~%mp;6 z!`Rm*zC$xiglWwYEt9nv@f=^9hQ&l<)t%IR4t`KS+NL9WXMmJCFHKOO)-{tVU^>N zd3uXV_cGnH{Mt%4Th3xw-TMOG)SkdfBv*^{MrHfjR9&qt`M{?8P?WegI)%FB?+lcn z5pWqAaj6F*v2fM>$!uWR_^JI0V8;=vjg@lzzLzvD2)wna?kCA7wFG6WVtNOhrF^H< zVUG1uydQSf3h_w2zH~lH>TZDun02(>=8Bd5KK?Fry9zJM#D9A3;mI+QA z=_}E)w)+p+n1E-sp;D!PZ;&z&73xK}23;KwV0#?~dz3?cealLpQ1dR`Pau5`G#S9;sF-GZP%RH+m5cV(gIsr@DS%H=6I7;Fli?73zlW%&+LR(3vcHO<+5OhHp7x^iu3P806 zI~uT^1n(v@lb=%jB6X*}NQ;{#-J3ONs?bBBa?Sy zd~>Vd#BOi|DpgBZxxsaLB~i~^{c;?R%WsQ2^M?=;gl=-cfTyiPtRToQW}kyYJ{26Yq)G zl8G=cpr!W2RwCL0^)Cs&7Ofl?y`oMpqT#C{N4D+Llz#^k1r-mM@vw}rAjoC@?%^o* z3n@nN*dIoRE?MLN12(4u#UkLl@bVlwt?k-upCS~3Its$#q>lwWpHgtDNPqZRpu`U3 z=h#w#f)G77+aZmDHYipncdH`axONjzLDbFL)9S?QFxdP}JODnD3O963RMVBbb`)%0 zXYD-eR}wnCLK*2K)U0pEy&r)i7acV|8~paO;LNtHdY{tLGIO;+Rovl1hB^<}30+p` z+NMaou(M)LXp`bZc0(xj6!OMS=^xa@%;JQc~iiO*%ZgyjxF;# zb3U+sawj>omlCnYHKa4JiOnrmu)Ny70{9q$^o4p@v zxffFKbte@IZYnjeK=M_z6&UeeUyp8M93|!JJCW-2Wa_pOB+Jolt%;PQVrTWe)_p2% ztby5e?+#Ctfi%3s3#%-Rb}{UPKxAc=Kb&@rHk`}K)7o#Uu8$W-l0E?0bBo`eBSwQ1 zC^HZhf(ldM^<)*l@-rW-z^8^Qjgrz>0#?R@1_2hV2YYEi`B*u!C8mSJ3KOvlThqTlmpQpiT1CTTO}4(BO&aWK{n(wth<#fv+EU z|2dHYSYcKuq|Hl`F&6PAjbCFIG);klovUj+E}iGf@97kzn&gE$scKE*)lW6u9>JZ@ zr*Y<7Sx^35SJuMnd3*#UY@HHZLq93)eb>iJM|WG%o7pBu0-Ju! zAe5|8ig;IN3qN*)E=D@t+fx_alM0!zAV&G>L~>HszZtWYs@O-Qpkll*(GJQWP&xd> z6?P2cqqjd|og7K!KU(hz(Eh2dRv4F?gJNfX6=#!3&iVOp&A2nI+^CJmE2pOBW={vE zw$qZEk5<)T`dSf;0yTw#i>MtHF;o$m@hr2XLJ}`s^}gXnMq2<;Q4ZIYShscYfu8@5 z7^LJ&gDTOHO?ae$9fQ;yd?SJ-8Rr_)%1Zu?J;3eR8Cj4rL+-RdbAphX#2DHf8g`=F zl22<$quW$-cJ7oXhKU1(Y>-$9ib_e!UyX|Te^qMMqW4e-mF^xqHr9$737v^hn*f z{bDdL%kw(^r0Y3qinkXf-3uzb7`;>-RFbl3v=Vpah(x6}V785&?nBQMw;yAJ{!v~As-brvj}vC`X%}qzTpj9ZNTqHFWCbpR91IXS?xG! zwN1q=^$#VbfX0UMFD(HToJtUozuJs7k1W5pgT%A%2SiKk5n_8qw4Ai*SXfE$e67x$ zu{4}*NZLAz&eBE=7Aw#0s7SL{R>ngfDC_Bpb61C?RMiR3buQz5qHmHe)>$RfT>*n^ z_kMqy)q%Z@%_<9&GdcbMcmV-Kv3U}2H-rFaa2Bkxbe4}+j&#QJ+*#Z~YN;`NoL*J3 z;4;C$Efi8DQ@E8Nhh? zdy3cVw-z)5cuca01D8dXjWSMI%DByz8DW)q!vI!VP$JE0YiQPP{Xk!adC zT9gV#>D9m#O?_7<&()gFS5&S%B}b#bh1{gFtmL3>_7q1!AJyHjYvYIey*q(2i?M{@ zf%8;`dm|A1gEzuYQ1%8knV{o*Sx%~^!+{~BJvk}m8U0__DvSye!uPBzf(#stTk8h# z0kRefkWt(82KJb<662D_cy;*oi7ZN9hJuELKJ zu@P2JzC+iJk8fHTvl<(t4ZdU8NARXk(I4Y~EVdO-l|#Qk;bPR(*(5JM1@lNMsU2F| z215NW>}7iFZi<#`llAL089OtpN&1auR3%Pego@(Mtg@oYPRWzKT>nO)jsL<)!J$Pj zn+j3vGD{lOuyv7Hgp?E&<5P;xjz*jC?5xTt&sLycn*pA+g;k=KfaAtGPk>KbEm#{= zvG2DTtO--tqdGMBF#*FHJN)wH8g)iQq;U#9F3US~OILA38<9pll*EgsiVqnJBIk8B zGUF=HItxWb^X*dF??7NAF83|Hf?w%C$R6)vc{bdWPNgML@KFz1{Z>kfuSSmER;wwR zm{bEEGh08}uDYdgc;c5WTKO&c3Grmm+F}FZK;vTSZRlY(xO3USVM(I&8=7F=esXOqt^3IqX{XW8hKOtJ+ zcp|jCKYK9deTK@^&ymRzX}$%id@t)gSYZ{1;g3+cd{CHXMX_@W-wDk5nh;V^@{O>H zK@PCmSb*g0^Q#MigQjenfDOl)C^Avo`-H7dI&IbICDEN#j6o?W@p5nr@n* zh>dKw3Xg>smSM4yh$;q!UG#$1y?yjlm3YOc)f%g=0pC?*j53OqztFF^yZKuo^w*mr zMzMOf$C5iII`(@N4mB&DX5^$#+i@nAAwk|GWx#cY6LRk4YBbSl+n+5-lTx>#Ucndv zc1q2ggmFQ-G|^}wjaKLD!({+ot2RUXtTNmlAmQIoGIwem0h$dk`;PiIHcHdyD+mhl zup0%Djm>*d?k|!XG1AtCPL@iPagPR#Xe)Q_YsGxjVP?I2M?&z#+gH3gdBOOYoRdl6 zMM`dPF|5xT5k0G7YCPW24+}Sn zvJP@_2;}+7BQ*Ai7} zKTrl-b_My{sT8DqzxuIdzYabf2Gm-FU@$+72)eD*EB2~S;E23h0-d}^n)+2L9`S*S><09*59)2u-izHc?XKSH30}Q?)wrHpTup zsS;?nCIxh9R5Y|4pj>HCYgi5l0>r)B>El}^yl)f9;x1Tj@O*wDRvVN*j9vsF<7`S| z6AaQ{W`UYHe_WG7&A;y(G;gVZ$o0-@$$34cx{tX;z{MiRP~2cN;>M})eq;iU6zvEY zYAI{(ekn=fBvO0S%7U6D*A9`()U=28y{wf4KFMcT2PA;XuRwM}S?xBrBu!-Fi8+De zGtm?4Y^I@G5xa39@vTPvXGqvLDYfy4WT&HAy}+$Z2CL;jd@hIW(EC*8Opnb)w z=bICy%hvIBCm(=?(K7flu6tZ!HYwS{K2cbF7D_eJ(Sn9{9*4WM)SbJD$KPOIj;Uwq z>&fb~)snM^J69h&op9y3vHIeTtYiP>Z;i|=;{f<&;^{vF6DjxR=y^S z=x5G-_{2L)SQ@a1k=^|DFXfP4in2bl;t{E+;13QxpSDq{W_CT|z$d11|0$ls#$ zaW%OglnnGy^4^NrH+KAeLsp=7B1Ml|94!z9W>uUSgN0LkTWAT|S?jMZ^c$vXueebW zS2GnT!aVtSPG}Q8fR}ZCh%FdWpvgOuQp*Hu!T1+}QN$ckvYQFQy*#8^UaNHA z5lQfXmIEqg+V2Xg%8Acr^0xBbQ>Hg$l+-1PM@MdRhsgs@gZrUUI?uQ#$eZ9x) z-L31H3TE*mjr|F!wyppizr78e7$8J#=L8#yyW%{f?zu4U48C)w%bY`(QmU%`G7f2n zLQGiO$<wQ{s|pETYV#@tt9J-a_j() zWx@!TjLB}j-_K}6&P&}H%~#PU8CWB|GEP+J+=?jjh0zVKjyg_s^F z2TVC*;ABvNa~6|w*N*B5Mc$A^r3~=q#rqcan31M)iJTQvgBxxv*Z>}54+^PD%RpY{ zTjpm=EV{WAnOA%tg2vrM0cawM5~t)@3$_nyW$lpm$v>NlQ~_%n#-Ag;08v1$zuI*U zj3E&}&F4gLWl7X19i@b??a|Q00q9Bj_l$JgLW#WyfP}+XVFO9eV1F}8V^>VDd(Isz zDyZdJsBC`mNBwx6t`m$!`KXkbn02sG%utwBCnr@CU) ztQ;FrzLO!sN_>=yL36~AEsUTbr;E-2r?}nioWiPZg-ZEJ&Z{TQ@T$l?LQZ1XG@U zK9fUhJ73^sgsi?GsMT1xJ5{miC|i~eX5$4Z$abB;d>dW3M`V^d)60u_Y3CXx$*MFT zWb)Ut#FOQKP5vY#61@D!7`%$iS?wV6OGg5sP;%jpAEAz8StkzP_`{@cdmax`iXLoW z6+%XfVNkUlbA0Q}1eP_%(?Ahy%}p_9T^D`%SEun{y8B6J5lS%kOxW$s5EE2ie_h_Z zk_bb;cm_5a4WFvm3VWp>W!odrI=ybg22s^}ps_6xZN!#t|12Gks<EWLQz6IH@XILAFd zrlIRND+`w)4qpLq!x_?gbj;TS;1e`VvcN@wr1P|{UrAHc@)}-ri{P^^aZfklK~*_- zeQ?~P+$n(-21|+Hdmt@PVDEw?arZB>ap>>#4#CLrHf!i=^qt$QOjuG~lrv?l5sl>A zE*7*Bm=vcv*0qC|sw`P=vn>WdtG((aJ%6$jRjMcumv#PSM!Mud)KS^m(+yUV@J)NkxJL`5 zU^wHcqQOVmYkUekvs$%qK3K)*A|zxvRv}*@h$5W|o4^7)v*RVlMcR#r9|lbN%KoX~9$Ddrlr!+%In+mpwc!tfa*YFN^< zR%q`P%5;+g5}a!lRy{GB_GDbG|8Y|`*E?xM#WNtARRy>cqu(-r2sjdc8Zs{eJf;hB zqUh%gdTCp$x-HCZCQ8&uR4n`4X8nvEu~?e|r%!CAp^RFi*y{3eU6#>_{Wi;NhSgH4 z@H6?BTre>zuB(h`%9mCt^_Q}}&jsBuk>e)8$S1!Qew>x?lLNZO=ML@DMpN@2{~53S z-kl0cr6h6!b$Q4Tr0i6t8{h%1B^25+g^+l0g4C|BSA&|=N4>#B?dE$D$ zGO~Eo=|n=S;>zq%&QQkP;=jKEp<%0T+(g?smTk zBmdkUiSC3DqF^ge@%p7}OOMkz2GrOzhJzO6K1yk{WRjeAj0uTPoQ|%Bn{P&FHr|XV z%gVWqKvDd}w%a+%onA3`Oj9AWJUycg*Ya?#tCGv?=Bo$t!*}REliEf)Ee(*57&|Ff zot1tNfy0nNfLD%P*gBBF4Z{UmREXo#3#yWVzH=A~0T&7q|{mq^XEO zHfVYRB8>dxT3{s)9mXfKOD@-32qAPDiI_Kfw8v@HVC(#7AN5dY#$dV%XEfE=0txE8 zB)b$heOpoa3ucUPhm(D=0ncE#=4gzf$h<)}*5*GvT1-(O8y3i32J7t*%BF264MNND z-5ch@qN?du>zA`8@L4%6=x!{_5mT2WKcUeA|39D_TP`}fJ%oot-BmE6?Yrd zY+b5}S@#y{buFDM<16FTN>UXj|BkMV1f6{xX!xdrKJC>knRvxH)3=Co-)6(O&}qc* zdIAjq+HuQK0`+pr64+M^p$XEF>NRLY2YMx-Zn<+9pYk-y(Qf&Vm$NNQ#cqN@6p|-x z=uNN++&*0jf_bN$%nP-#<^jV14*gE2Y4Gkr8*Lu{*6)RsAP}YB36y8zcP8(I6xIF9 zuvS63AoI&X^&V~jyhe3hOOK1!7&P+aQyU4 z6b%Y&RmEzrwH#)W;?-ZM2));vin`7=`bxW^cbrC{0KqS-XOE4iFTS4Zbff3$3j(;$ z>8CS`XLzxNGhFA``$clM9dFY}viJTdPvYNrF}Yd^d;E@MTBV0-0|Y_! zZKl-mMTc@3o}`GBwowISdpe;+IDyctM6^X0F!Qo~_)Wg?D+8d6Bi9>2g?X$H&7|_> z1hT-zkS>S8JL(jSxU~H&L$$!j+!tMWEYC>=YuOy(R#C!Ui%H$BFl$QKQVBVG-oe?> z&fxVj*-^B>r>I+QbXiK$geeVy*_=);mt86SZqCR9qWa=jMQ2HGO0jU$XhW-ak?pRQ z5bb?AX@L6IK}ODUU_-HZ&p>Hft8#mtuSy5;b)(cf62c(CUU?-ph-5p1k66J=IygGP ze;z{;dXZS);F{flEyEwa9hOL7ZY})2vY1=+ZQ@j1c`WZ}F+6R11jfg2b-~JmghXSA zGSt3Ic9jGm^tU$8%C)t(YMk^W1YKVi=^8v}=FH0)s4Sb=o>w8|s~ny^*wP-U(qCG0 zx}6RfuRY=vGTpHP%J@>zGeZ!_n~x4=G5_7L2D9AZU%$nXu_`LJXVbe`j8@sbEvGao>z8G2tXS%y60sHP~a` z4g5el>SrqVw)Kf)ZoIOxDs|2&2&Ik@(W{A16^nh6+OSNUSgepY&(I?dog3Meq#2~0 zz@^e3KYORun)#Xg_T#yi1g2|c;yl&Otx&2%ujnzIgW|Q~Mu-~eRJc_KT##dmr?tU= z=yPs~<(`ZGIQOU!UM-gK81a?zA&(%bUa8u|ftmGnJe!QuvbpJ(LKy49RxqbRWbYpG z4ZLatbM+}ajw{MQgF5W&iu(=$&HJ}gJN4n5E|=MqJ+Jdc9Q_9kpc#c68;s zEL!p3qHpV+T_Us#?doJT;U*X`Olo{0t%3PPKyL>QdLAo-b?uzo>vF|k@bwPdxQHC5 zsA+pd#+CfOMqSz=j4?4jDGGEbt7RdtFOwj~0zo5q?alVli1TAc(wnxqNI-L00#wG? zr-!};?R(Nl*VH9X%eAo^u`+&I*{o16jHAG`=1$To#45NU5WRb?mQ9 zMQ0mZf(#ULy9vI6D7lvcqw!jGEJzCreN{y{2AG{RNz&-LwmS2#VuoLhaz4d!`*QRZ zqK!kBT*=lSS7e86)Z_HsaUWqvG~$j$qVF{v6DVT}qDi0*d85psygBQ-N_p3Z7K3p!5?

    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~ 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%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? 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 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 496/773] [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 497/773] [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 498/773] [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 499/773] [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 500/773] 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 501/773] [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 502/773] [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 503/773] [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 504/773] [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 505/773] [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 506/773] [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 507/773] [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 508/773] [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 509/773] [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 510/773] [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 511/773] [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 512/773] [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 513/773] 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 514/773] 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 515/773] 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 516/773] [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 517/773] [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 518/773] [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 519/773] [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 520/773] [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 521/773] [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 522/773] [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 523/773] [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 524/773] [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 525/773] [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 526/773] [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 527/773] [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 528/773] [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 529/773] [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 530/773] [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 531/773] [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 532/773] [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 533/773] [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 534/773] [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 535/773] [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 536/773] [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 537/773] [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 538/773] [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 539/773] [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 540/773] [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 541/773] 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 542/773] 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 543/773] [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 544/773] 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 545/773] [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 546/773] 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 547/773] 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 548/773] 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 549/773] 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 550/773] [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 551/773] [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 552/773] [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 553/773] [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 554/773] [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 555/773] [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 556/773] 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 557/773] [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 558/773] [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 559/773] [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 560/773] [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 561/773] [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 562/773] 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 563/773] [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 564/773] [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 565/773] [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 566/773] [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 567/773] [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 568/773] 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 569/773] 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 570/773] [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 571/773] [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 572/773] [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 573/773] [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 574/773] [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 575/773] [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 576/773] 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 577/773] 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 578/773] [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 579/773] [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 580/773] [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 581/773] 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 582/773] [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 583/773] 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 584/773] [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 585/773] [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 586/773] 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 587/773] [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 588/773] [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 589/773] [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 590/773] [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 591/773] [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 592/773] 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 593/773] 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 594/773] [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 595/773] [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 596/773] [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 597/773] [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 598/773] [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 599/773] [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 600/773] [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 601/773] [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 602/773] 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 603/773] 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 604/773] [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 605/773] [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 606/773] [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 607/773] [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 608/773] [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 609/773] [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 610/773] [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 611/773] [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 612/773] [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 613/773] [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 614/773] [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 615/773] [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 616/773] [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 617/773] [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 618/773] [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 619/773] [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 620/773] [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 621/773] [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 622/773] [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 623/773] [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 624/773] [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 625/773] [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 626/773] [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 627/773] 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 628/773] [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 629/773] 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 630/773] [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 631/773] 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 632/773] 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 633/773] 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 634/773] 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 635/773] 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 636/773] [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 637/773] [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 638/773] [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 639/773] [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 640/773] [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 641/773] [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 642/773] [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 643/773] [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 644/773] [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 645/773] [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 646/773] [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 647/773] [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 648/773] 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 649/773] 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 a5566f5d5a3ee47a760cf27a77875144689d8b96 Mon Sep 17 00:00:00 2001 From: alja Date: Thu, 12 Oct 2023 14:38:41 -0700 Subject: [PATCH 650/773] Implement only-if-cached cache control using XrdPfcFsctl --- src/XProtocol/XProtocol.cc | 3 ++- src/XProtocol/XProtocol.hh | 3 +++ src/XrdHttp/XrdHttpProtocol.cc | 5 +++++ src/XrdHttp/XrdHttpReq.cc | 3 +++ src/XrdPfc/XrdPfcFSctl.cc | 18 ++++++++++++++++++ src/XrdPss/XrdPss.cc | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/XProtocol/XProtocol.cc b/src/XProtocol/XProtocol.cc index 1e1772d3fb0..da9e7461349 100644 --- a/src/XProtocol/XProtocol.cc +++ b/src/XProtocol/XProtocol.cc @@ -102,7 +102,8 @@ const char *errNames[kXR_ERRFENCE-kXR_ArgInvalid] = "Request is not possible", // kXR_Impossible "Conflicting request", // kXR_Conflict "Too many errors", // kXR_TooManyErrs - "Request timed out" // kXR_ReqTimedOut + "Request timed out", // kXR_ReqTimedOut + "Timer expired" // kXR_TimerExipred }; const char *reqNames[kXR_REQFENCE-kXR_auth] = diff --git a/src/XProtocol/XProtocol.hh b/src/XProtocol/XProtocol.hh index 561161d9913..b984f191d75 100644 --- a/src/XProtocol/XProtocol.hh +++ b/src/XProtocol/XProtocol.hh @@ -1020,6 +1020,7 @@ enum XErrorCode { kXR_Conflict, // 3032 kXR_TooManyErrs, // 3033 kXR_ReqTimedOut, // 3034 + kXR_TimerExpired, // 3035 kXR_ERRFENCE, // Always last valid errcode + 1 kXR_noErrorYet = 10000 }; @@ -1396,6 +1397,7 @@ static int mapError(int rc) case ETIMEDOUT: return kXR_ReqTimedOut; case EBADF: return kXR_FileNotOpen; case ECANCELED: return kXR_Cancelled; + case ETIME: return kXR_TimerExpired; default: return kXR_FSError; } } @@ -1438,6 +1440,7 @@ static int toErrno( int xerr ) case kXR_Conflict: return ENOTTY; case kXR_TooManyErrs: return ETOOMANYREFS; case kXR_ReqTimedOut: return ETIMEDOUT; + case kXR_TimerExpired: return ETIME; // Used for 504 Gateway timeout in proxy default: return ENOMSG; } } diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 0001b251fe3..ad0a282071a 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1085,6 +1085,10 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { return 1; } +// Some headers must always be converted to CGI key=value pairs +// + hdr2cgimap["Cache-Control"] = "cache-control"; + // Test if XrdEC is loaded if (getenv("XRDCL_EC")) usingEC = true; @@ -1561,6 +1565,7 @@ int XrdHttpProtocol::StartSimpleResp(int code, const char *desc, const char *hea 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 == 504) ss << "Gateway Timeout"; else ss << "Unknown"; } ss << crlf; diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 7d4beed9b75..9a54293b52f 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -898,6 +898,9 @@ void XrdHttpReq::mapXrdErrorToHttpStatus() { case kXR_InvalidRequest: httpStatusCode = 405; httpStatusText = "Method is not allowed"; break; + case kXR_TimerExpired: + httpStatusCode = 504; httpStatusText = "Gateway timeout"; + break; default: break; } diff --git a/src/XrdPfc/XrdPfcFSctl.cc b/src/XrdPfc/XrdPfcFSctl.cc index 1a2f1f29952..b86622bbb52 100644 --- a/src/XrdPfc/XrdPfcFSctl.cc +++ b/src/XrdPfc/XrdPfcFSctl.cc @@ -35,6 +35,7 @@ #include "XrdOfs/XrdOfsHandle.hh" #include "XrdOuc/XrdOucEnv.hh" #include "XrdOuc/XrdOucErrInfo.hh" +#include "XrdOuc/XrdOucCache.hh" #include "XrdPfc/XrdPfc.hh" #include "XrdPfc/XrdPfcFSctl.hh" #include "XrdPfc/XrdPfcTrace.hh" @@ -131,6 +132,23 @@ int XrdPfcFSctl::FSctl(const int cmd, rc = SFS_ERROR; } + if (!strcmp(xeq, "cached")) + { + const char* path = args.ArgP[0]; + int rval = myCache.LocalFilePath(path, nullptr, 0, XrdOucCache::LFP_Reason::ForInfo); + if (rval == 0 || rval == -EREMOTE) + { + rc = SFS_OK; + ec = 0; + } + else + { + ec = ETIME; + rc = SFS_ERROR; + TRACE(Info,"Cache "< Auth ID mapper static const char *ofslclCGI = "ofs.lcl=1"; @@ -187,6 +191,12 @@ int XrdPssSys::Init(XrdSysLogger *lp, const char *cFN, XrdOucEnv *envP) tmp = ((NoGo = Configure(cFN, envP)) ? "failed." : "completed."); eDest.Say("------ Proxy storage system initialization ", tmp); +// Extract Pfc control, if it is there. +// + if (!NoGo) + cacheFSctl = (XrdOfsFSctl_PI*)envP->GetPtr("XrdFSCtl_PC*"); + + // All done. // return NoGo; @@ -767,6 +777,28 @@ int XrdPssFile::Open(const char *path, int Oflag, mode_t Mode, XrdOucEnv &Env) } } + // check CGI cache-control paramters + if (cacheFSctl) + { + int elen; + char *envcgi = (char *)Env.Env(elen); + + if (envcgi && strstr(envcgi, "only-if-cached")) + { + XrdOucErrInfo einfo; + XrdSfsFSctl myData; + myData.Arg1 = "cached"; + myData.Arg1Len = 1; + myData.Arg2Len = 1; + const char *myArgs[1]; + myArgs[0] = path; + myData.ArgP = myArgs; + int fsctlRes = cacheFSctl->FSctl(SFS_FSCTL_PLUGXC, myData, einfo); + if (fsctlRes == SFS_ERROR) + return -einfo.getErrInfo(); + } + } + // If this is a third party copy open, then strange rules apply. If this is an // outgoing proxy we let everything pass through as this may be a TPC request // elsewhere. Otherwise, if it's an open for reading, we open the file but From 285951c689a4e62c4a0008e247e067e619b69708 Mon Sep 17 00:00:00 2001 From: alja Date: Fri, 13 Oct 2023 09:56:05 -0700 Subject: [PATCH 651/773] Return a success status only if the file is completely cached --- src/XrdPfc/XrdPfcFSctl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdPfc/XrdPfcFSctl.cc b/src/XrdPfc/XrdPfcFSctl.cc index b86622bbb52..7cbcd3f36e1 100644 --- a/src/XrdPfc/XrdPfcFSctl.cc +++ b/src/XrdPfc/XrdPfcFSctl.cc @@ -136,7 +136,7 @@ int XrdPfcFSctl::FSctl(const int cmd, { const char* path = args.ArgP[0]; int rval = myCache.LocalFilePath(path, nullptr, 0, XrdOucCache::LFP_Reason::ForInfo); - if (rval == 0 || rval == -EREMOTE) + if (rval == 0) { rc = SFS_OK; ec = 0; From e209717eac58fdbd571f7872abcb1e87f0ef039f Mon Sep 17 00:00:00 2001 From: alja Date: Thu, 26 Oct 2023 21:32:09 -0700 Subject: [PATCH 652/773] Add minsize and minfrac configuration options to tune only-if-cached decisions. --- src/XrdPfc/XrdPfc.cc | 98 +++++++++++++++++++++++++++++++ src/XrdPfc/XrdPfc.hh | 9 +++ src/XrdPfc/XrdPfcConfiguration.cc | 55 ++++++++++++++++- src/XrdPfc/XrdPfcFSctl.cc | 2 +- 4 files changed, 160 insertions(+), 4 deletions(-) diff --git a/src/XrdPfc/XrdPfc.cc b/src/XrdPfc/XrdPfc.cc index ab0fbcbf114..bbbcb52909b 100644 --- a/src/XrdPfc/XrdPfc.cc +++ b/src/XrdPfc/XrdPfc.cc @@ -896,6 +896,104 @@ int Cache::LocalFilePath(const char *curl, char *buff, int blen, return -ENOENT; } +//______________________________________________________________________________ +// Check if the file is cached including m_onlyIfCachedMinSize and m_onlyIfCachedMinFrac +// pfc configuration parameters. The logic of accessing the Info file is the same +// as in Cache::LocalFilePath. +//! @return 0 - the file is complete and the local path to the file is in +//! the buffer, if it has been supllied. +//! +//! @return <0 - the request could not be fulfilled. The return value is +//! -errno describing why. If a buffer was supplied and a +//! path could be generated it is returned only if "why" is +//! ForCheck or ForInfo. Otherwise, a null path is returned. +//! +//! @return >0 - Reserved for future use. +//------------------------------------------------------------------------------ +int Cache::ConsiderCached(const char *curl) +{ + TRACE(Debug, "ConsiderFileCached '" << curl << "'" ); + + XrdCl::URL url(curl); + std::string f_name = url.GetPath(); + std::string i_name = f_name + Info::s_infoExtension; + + { + XrdSysCondVarHelper lock(&m_active_cond); + m_purge_delay_set.insert(f_name); + } + + struct stat sbuff, sbuff2; + if (m_oss->Stat(f_name.c_str(), &sbuff) == XrdOssOK && + m_oss->Stat(i_name.c_str(), &sbuff2) == XrdOssOK) + { + if (S_ISDIR(sbuff.st_mode)) + { + TRACE(Info, "ConsiderCached '" << curl << ", why=ForInfo" << " -> EISDIR"); + return -EISDIR; + } + else + { + bool read_ok = false; + bool is_cached = false; + + // Lock and check if the file is active. If NOT, keep the lock + // and add dummy access after successful reading of info file. + // If it IS active, just release the lock, this ongoing access will + // assure the file continues to exist. + + // XXXX How can I just loop over the cinfo file when active? + // Can I not get is_complete from the existing file? + // Do I still want to inject access record? + // Oh, it writes only if not active .... still let's try to use existing File. + + m_active_cond.Lock(); + + bool is_active = m_active.find(f_name) != m_active.end(); + + if (is_active) + m_active_cond.UnLock(); + + XrdOssDF *infoFile = m_oss->newFile(m_configuration.m_username.c_str()); + XrdOucEnv myEnv; + int res = infoFile->Open(i_name.c_str(), O_RDWR, 0600, myEnv); + if (res >= 0) + { + Info info(m_trace, 0); + if (info.Read(infoFile, i_name.c_str())) + { + read_ok = true; + + if (info.IsComplete()) + { + is_cached = true; + } + else + { + long long fileSize = info.GetFileSize(); + long long bytesRead = info.GetNDownloadedBytes(); + if (bytesRead > m_configuration.m_onlyIfCachedMinSize && + (float)bytesRead/fileSize > m_configuration.m_onlyIfCachedMinFrac) + is_cached = true; + } + } + infoFile->Close(); + } + delete infoFile; + + if (!is_active) m_active_cond.UnLock(); + + if (read_ok) + { + TRACE(Info, "ConsiderCached '" << curl << "', why=ForInfo" << (is_cached ? " -> FILE_COMPLETE_IN_CACHE" : " -> EREMOTE")); + return is_cached ? 0 : -EREMOTE; + } + } + } + + TRACE(Info, "ConsiderCached '" << curl << "', why=ForInfo" << " -> ENOENT"); + return -ENOENT; +} //______________________________________________________________________________ //! Preapare the cache for a file open request. This method is called prior to diff --git a/src/XrdPfc/XrdPfc.hh b/src/XrdPfc/XrdPfc.hh index 6d009c81c8e..fdc4093dd70 100644 --- a/src/XrdPfc/XrdPfc.hh +++ b/src/XrdPfc/XrdPfc.hh @@ -111,6 +111,9 @@ struct Configuration time_t m_cs_UVKeep; //!< unverified checksum cache keep int m_cs_Chk; //!< Checksum check bool m_cs_ChkTLS; //!< Allow TLS + + long long m_onlyIfCachedMinSize; //!< minumum size of downloaded file, used by only-if-cached CGI option + double m_onlyIfCachedMinFrac; //!< minimum fraction of downloaded file, used by only-if-cached CGI option }; //------------------------------------------------------------------------------ @@ -291,6 +294,12 @@ public: // virtual function of XrdOucCache. virtual int Unlink(const char *url); + //--------------------------------------------------------------------- + // Used by PfcFstcl::Fsctl function. + // Test if file is cached taking in onlyifcached configuration parameters. + //--------------------------------------------------------------------- + virtual int ConsiderCached(const char *url); + //-------------------------------------------------------------------- //! \brief Makes decision if the original XrdOucCacheIO should be cached. //! diff --git a/src/XrdPfc/XrdPfcConfiguration.cc b/src/XrdPfc/XrdPfcConfiguration.cc index 0dcb4f5e5ee..8f4bcd064ed 100644 --- a/src/XrdPfc/XrdPfcConfiguration.cc +++ b/src/XrdPfc/XrdPfcConfiguration.cc @@ -46,7 +46,9 @@ Configuration::Configuration() : m_flushCnt(2000), m_cs_UVKeep(-1), m_cs_Chk(CSChk_Net), - m_cs_ChkTLS(false) + m_cs_ChkTLS(false), + m_onlyIfCachedMinSize(1024*1024), + m_onlyIfCachedMinFrac(1.0) {} @@ -488,7 +490,9 @@ bool Cache::Config(const char *config_filename, const char *parameters) " pfc.spaces %s %s\n" " pfc.trace %d\n" " pfc.flush %lld\n" - " pfc.acchistorysize %d\n", + " pfc.acchistorysize %d\n" + " pfc.onlyIfCachedMinBytes %lld\n" + " pfc.onlyIfCachedMinFrac %.2f\n", config_filename, csc[int(m_configuration.m_cs_Chk)], uvk, m_configuration.m_bufferSize, @@ -503,7 +507,9 @@ bool Cache::Config(const char *config_filename, const char *parameters) m_configuration.m_meta_space.c_str(), m_trace->What, m_configuration.m_flushCnt, - m_configuration.m_accHistorySize); + m_configuration.m_accHistorySize, + m_configuration.m_onlyIfCachedMinSize, + m_configuration.m_onlyIfCachedMinFrac); if (m_configuration.is_dir_stat_reporting_on()) { @@ -825,6 +831,49 @@ bool Cache::ConfigParameters(std::string part, XrdOucStream& config, TmpConfigur return false; } } + else if ( part == "onlyifcached" ) + { + const char *p = 0; + while ((p = cwg.GetWord()) && cwg.HasLast()) + { + if (strcmp(p, "minsize") == 0) + { + std::string minBytes = cwg.GetWord(); + long long minBytesTop = 1024 * 1024 * 1024; + if (::isalpha(*(minBytes.rbegin()))) + { + if (XrdOuca2x::a2sz(m_log, "Error in parsing minsize value for onlyifcached parameter", minBytes.c_str(), &m_configuration.m_onlyIfCachedMinSize, 0, minBytesTop)) + { + return false; + } + } + else + { + if (XrdOuca2x::a2ll(m_log, "Error in parsing numeric minsize value for onlyifcached parameter", minBytes.c_str(),&m_configuration.m_onlyIfCachedMinSize, 0, minBytesTop)) + { + return false; + } + } + } + if (strcmp(p, "minfrac") == 0) + { + std::string minFrac = cwg.GetWord(); + char *eP; + errno = 0; + double frac = strtod(minFrac.c_str(), &eP); + if (errno || eP == minFrac.c_str()) + { + m_log.Emsg("Config", "Error setting fraction for only-if-cached directive"); + return false; + } + m_configuration.m_onlyIfCachedMinFrac = frac; + } + else + { + m_log.Emsg("Config", "Error: onlyifcached stanza contains unknown directive", p); + } + } + } else { m_log.Emsg("ConfigParameters() unmatched pfc parameter", part.c_str()); diff --git a/src/XrdPfc/XrdPfcFSctl.cc b/src/XrdPfc/XrdPfcFSctl.cc index 7cbcd3f36e1..2298bfd75b5 100644 --- a/src/XrdPfc/XrdPfcFSctl.cc +++ b/src/XrdPfc/XrdPfcFSctl.cc @@ -135,7 +135,7 @@ int XrdPfcFSctl::FSctl(const int cmd, if (!strcmp(xeq, "cached")) { const char* path = args.ArgP[0]; - int rval = myCache.LocalFilePath(path, nullptr, 0, XrdOucCache::LFP_Reason::ForInfo); + int rval = myCache.ConsiderCached(path); if (rval == 0) { rc = SFS_OK; From a1506f28c988e7f10bb72e91ec5d4c412f68a87e Mon Sep 17 00:00:00 2001 From: alja Date: Mon, 30 Oct 2023 11:50:02 -0700 Subject: [PATCH 653/773] In cache::ConsiderCached handle case where file is zero size and file size is smaller than pfc.onlyifcached minsize --- src/XrdPfc/XrdPfc.cc | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/XrdPfc/XrdPfc.cc b/src/XrdPfc/XrdPfc.cc index bbbcb52909b..d3766daee64 100644 --- a/src/XrdPfc/XrdPfc.cc +++ b/src/XrdPfc/XrdPfc.cc @@ -968,13 +968,26 @@ int Cache::ConsiderCached(const char *curl) { is_cached = true; } + else if (info.GetFileSize() == 0) + { + is_cached = true; + } else { long long fileSize = info.GetFileSize(); long long bytesRead = info.GetNDownloadedBytes(); - if (bytesRead > m_configuration.m_onlyIfCachedMinSize && - (float)bytesRead/fileSize > m_configuration.m_onlyIfCachedMinFrac) + + if (fileSize < m_configuration.m_onlyIfCachedMinSize) + { + if ((float)bytesRead / fileSize > m_configuration.m_onlyIfCachedMinFrac) is_cached = true; + } + else + { + if (bytesRead > m_configuration.m_onlyIfCachedMinSize && + (float)bytesRead / fileSize > m_configuration.m_onlyIfCachedMinFrac) + is_cached = true; + } } } infoFile->Close(); From 28b6256535f6b9432d41b46290cb721531488686 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 5 Sep 2023 11:59:00 +0200 Subject: [PATCH 654/773] [XrdCrypto,XrdSecgsi] Update min/default RSA bits to 2048 --- docs/man/xrdgsiproxy.1 | 2 +- src/XrdCrypto/XrdCryptoAux.hh | 4 ++-- src/XrdCrypto/XrdCryptoFactory.hh | 2 +- src/XrdCrypto/XrdCryptosslRSA.cc | 4 ++-- src/XrdCrypto/XrdCryptosslgsiAux.cc | 8 ++++---- src/XrdSecgsi/XrdSecProtocolgsi.cc | 6 +++--- src/XrdSecgsi/XrdSecProtocolgsi.hh | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/man/xrdgsiproxy.1 b/docs/man/xrdgsiproxy.1 index c63517a69ab..3f9a10e69ea 100644 --- a/docs/man/xrdgsiproxy.1 +++ b/docs/man/xrdgsiproxy.1 @@ -41,7 +41,7 @@ Non-standard location of certificate for which proxies are wanted 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] +Strength in bits of the key [default 2048] .TP \fB-valid\fR \fIhh:mm\fR Time validity of the proxy certificate [default 12:00] diff --git a/src/XrdCrypto/XrdCryptoAux.hh b/src/XrdCrypto/XrdCryptoAux.hh index cbc810ff7ff..d3fa90c082a 100644 --- a/src/XrdCrypto/XrdCryptoAux.hh +++ b/src/XrdCrypto/XrdCryptoAux.hh @@ -49,8 +49,8 @@ #define cryptoTRACE_Notify 0x0001 // RSA parameters -#define XrdCryptoMinRSABits 512 -#define XrdCryptoDefRSABits 1024 +#define XrdCryptoMinRSABits 2048 +#define XrdCryptoDefRSABits 2048 #define XrdCryptoDefRSAExp 0x10001 /******************************************************************************/ diff --git a/src/XrdCrypto/XrdCryptoFactory.hh b/src/XrdCrypto/XrdCryptoFactory.hh index 20c2e1096fb..4d8e5a9d379 100644 --- a/src/XrdCrypto/XrdCryptoFactory.hh +++ b/src/XrdCrypto/XrdCryptoFactory.hh @@ -98,7 +98,7 @@ typedef bool (*XrdCryptoProxyCertInfo_t)(const void *, int &, bool *); typedef void (*XrdCryptoSetPathLenConstraint_t)(void *, int); // create a proxy certificate typedef struct { - int bits; // Number of bits in the RSA key [512] + int bits; // Number of bits in the RSA key [2048] 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 diff --git a/src/XrdCrypto/XrdCryptosslRSA.cc b/src/XrdCrypto/XrdCryptosslRSA.cc index 54818139ac3..ae49406d61d 100644 --- a/src/XrdCrypto/XrdCryptosslRSA.cc +++ b/src/XrdCrypto/XrdCryptosslRSA.cc @@ -88,7 +88,7 @@ XrdCryptosslRSA::XrdCryptosslRSA(int bits, int exp) { // Constructor // Generate a RSA asymmetric key pair - // Length will be 'bits' bits (min 512, default 1024), public + // Length will be 'bits' bits (min 2048, default 2048), public // exponent `pubex` (default 65537). EPNAME("RSA::XrdCryptosslRSA"); @@ -96,7 +96,7 @@ XrdCryptosslRSA::XrdCryptosslRSA(int bits, int exp) prilen = -1; // Minimum is XrdCryptoMinRSABits - bits = (bits >= XrdCryptoMinRSABits) ? bits : XrdCryptoMinRSABits; + bits = (bits >= XrdCryptoMinRSABits) ? bits : XrdCryptoDefRSABits; // If pubex is not odd, use default if (!(exp & 1)) diff --git a/src/XrdCrypto/XrdCryptosslgsiAux.cc b/src/XrdCrypto/XrdCryptosslgsiAux.cc index a88dbd900b8..98548a30b88 100644 --- a/src/XrdCrypto/XrdCryptosslgsiAux.cc +++ b/src/XrdCrypto/XrdCryptosslgsiAux.cc @@ -286,7 +286,7 @@ int XrdCryptosslX509CreateProxy(const char *fnc, const char *fnk, ERR_load_crypto_strings(); // Use default options, if not specified - int bits = (pxopt && pxopt->bits >= 512) ? pxopt->bits : 512; + int bits = (pxopt && pxopt->bits >= XrdCryptoMinRSABits) ? pxopt->bits : XrdCryptoDefRSABits; int valid = (pxopt) ? pxopt->valid : 43200; // 12 hours int depthlen = (pxopt) ? pxopt->depthlen : -1; // unlimited @@ -696,13 +696,13 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, return -kErrPX_NoResources; } // - // Use same num of bits as the signing certificate, but - // less than 512 + // Use same num of bits as the signing certificate, + // but no less than the minimum RSA bits (2048) ekro.reset(X509_get_pubkey(xpi)); int bits = EVP_PKEY_bits(ekro.get()); ekro = nullptr; - bits = (bits < 512) ? 512 : bits; + bits = (bits < XrdCryptoMinRSABits) ? XrdCryptoDefRSABits : bits; // // Create the new PKI for the proxy (exponent 65537) BIGNUM *e = BN_new(); diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.cc b/src/XrdSecgsi/XrdSecProtocolgsi.cc index 562543d4523..ff067e9775f 100644 --- a/src/XrdSecgsi/XrdSecProtocolgsi.cc +++ b/src/XrdSecgsi/XrdSecProtocolgsi.cc @@ -149,7 +149,7 @@ 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::DefBits = XrdCryptoDefRSABits; int XrdSecProtocolgsi::CACheck = caVerifyss; int XrdSecProtocolgsi::CRLCheck = crlTry; // 1 int XrdSecProtocolgsi::CRLDownload = 0; @@ -2414,7 +2414,7 @@ char *XrdSecProtocolgsiInit(const char mode, // ["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] + // "XrdSecGSIPROXYKEYBITS" bits in PKI for proxies [default: XrdCryptoDefRSABits] // "XrdSecGSICACHECK" CA check level [1]: // 0 do not verify; // 1 verify if self-signed, warn if not; @@ -4889,7 +4889,7 @@ int XrdSecProtocolgsi::InitProxy(ProxyIn_t *pi, XrdCryptoFactory *cf, X509Chain } // Add number of bits in key - if (pi->bits > 512) { + if (pi->bits != XrdCryptoDefRSABits) { cmd += " -bits "; cmd += pi->bits; } diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.hh b/src/XrdSecgsi/XrdSecProtocolgsi.hh index 2c79f5aebe8..6c0f4fca1f3 100644 --- a/src/XrdSecgsi/XrdSecProtocolgsi.hh +++ b/src/XrdSecgsi/XrdSecProtocolgsi.hh @@ -188,7 +188,7 @@ public: 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] + int bits; // [c] bits in PKI for proxies [default: XrdCryptoDefRSABits] 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] @@ -218,7 +218,7 @@ public: 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; + proxy = 0; valid = 0; deplen = 0; bits = XrdCryptoDefRSABits; gridmap = 0; gmapto = 600; gmapfun = 0; gmapfunparms = 0; authzfun = 0; authzfunparms = 0; authzto = -1; authzcall = 1; From 9ef3a2a00b52105883613d2adb6d46a8409b2249 Mon Sep 17 00:00:00 2001 From: Chris Green Date: Wed, 22 Feb 2023 08:27:07 -0600 Subject: [PATCH 655/773] Make CMAKE_CXX_STANDARD a CACHE variable This allows CMAKE_CXX_STANDARD to be customized from the CMake CLI with (e.g.) `-DCMAKE_CXX_STANDARD=17`. --- cmake/XRootDOSDefs.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/XRootDOSDefs.cmake b/cmake/XRootDOSDefs.cmake index 475fcfe33da..5f1ebc71546 100644 --- a/cmake/XRootDOSDefs.cmake +++ b/cmake/XRootDOSDefs.cmake @@ -18,7 +18,7 @@ define_default( LIBRARY_PATH_PREFIX "lib" ) #------------------------------------------------------------------------------- # Enable c++14 #------------------------------------------------------------------------------- -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ Standard") set(CMAKE_CXX_STANDARD_REQUIRED TRUE) if( ENABLE_ASAN ) From 22247391468328ca6a73697177dc0cf0a95528cb Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 23 Mar 2023 10:57:30 +0100 Subject: [PATCH 656/773] Remove 'using namespace std;' from headers and source files This is necessary to avoid name clashes between the std and global namespaces. It was identified as a problem when trying to compile XRootD with C++17 standard and support for VOMS enabled, since VOMS has a struct named 'struct data' in the global namespace, which clashes with std::data from C++17. --- src/XrdApps/XrdCpConfig.cc | 1 - src/XrdApps/XrdCrc32c.cc | 1 - src/XrdApps/XrdMpxXml.cc | 1 - src/XrdApps/XrdQStats.cc | 1 - src/XrdNet/XrdNetIF.cc | 1 - src/XrdOuc/XrdOucNSWalk.cc | 1 - src/XrdOuc/XrdOucString.hh | 2 -- src/XrdSciTokens/vendor/picojson/examples/github-issues.cc | 1 - src/XrdSciTokens/vendor/picojson/test.cc | 1 - src/XrdSsi/XrdSsiLogging.cc | 1 - src/XrdSsi/XrdSsiShMam.cc | 1 - src/XrdSys/XrdSysHeaders.hh | 1 - src/XrdSys/XrdSysIOEventsPollE.icc | 1 - src/XrdSys/XrdSysIOEventsPollKQ.icc | 1 - src/XrdSys/XrdSysIOEventsPollPoll.icc | 1 - src/XrdSys/XrdSysIOEventsPollPort.icc | 1 - tests/XrdSsiTests/XrdShMap.cc | 1 - 17 files changed, 18 deletions(-) diff --git a/src/XrdApps/XrdCpConfig.cc b/src/XrdApps/XrdCpConfig.cc index 5981c4a9242..890f80198cc 100644 --- a/src/XrdApps/XrdCpConfig.cc +++ b/src/XrdApps/XrdCpConfig.cc @@ -47,7 +47,6 @@ #include "XrdSys/XrdSysHeaders.hh" #include "XrdSys/XrdSysLogger.hh" -using namespace std; /******************************************************************************/ /* D e f i n e M a c r o s */ diff --git a/src/XrdApps/XrdCrc32c.cc b/src/XrdApps/XrdCrc32c.cc index 0b5f26de86d..b213d6dc230 100644 --- a/src/XrdApps/XrdCrc32c.cc +++ b/src/XrdApps/XrdCrc32c.cc @@ -42,7 +42,6 @@ #include "XrdOuc/XrdOucCRC.hh" #include "XrdSys/XrdSysE2T.hh" -using namespace std; namespace {const char *pgm = "xrdcrc32c"; diff --git a/src/XrdApps/XrdMpxXml.cc b/src/XrdApps/XrdMpxXml.cc index 22284b98cdc..a8840c848cd 100644 --- a/src/XrdApps/XrdMpxXml.cc +++ b/src/XrdApps/XrdMpxXml.cc @@ -40,7 +40,6 @@ #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 */ diff --git a/src/XrdApps/XrdQStats.cc b/src/XrdApps/XrdQStats.cc index aa64ae85102..65263fd9e69 100644 --- a/src/XrdApps/XrdQStats.cc +++ b/src/XrdApps/XrdQStats.cc @@ -40,7 +40,6 @@ #include "XrdCl/XrdClURL.hh" #include "XrdCl/XrdClXRootDResponses.hh" -using namespace std; /******************************************************************************/ /* F a t a l */ diff --git a/src/XrdNet/XrdNetIF.cc b/src/XrdNet/XrdNetIF.cc index e7c0a51fbe5..5e06b79da12 100644 --- a/src/XrdNet/XrdNetIF.cc +++ b/src/XrdNet/XrdNetIF.cc @@ -47,7 +47,6 @@ #include "XrdSys/XrdSysError.hh" #include -using namespace std; /******************************************************************************/ /* L o c a l S t a t i c s */ diff --git a/src/XrdOuc/XrdOucNSWalk.cc b/src/XrdOuc/XrdOucNSWalk.cc index 34ca4accdd9..4904972c0df 100644 --- a/src/XrdOuc/XrdOucNSWalk.cc +++ b/src/XrdOuc/XrdOucNSWalk.cc @@ -40,7 +40,6 @@ #include "XrdSys/XrdSysHeaders.hh" #include "XrdSys/XrdSysPlatform.hh" -using namespace std; /******************************************************************************/ /* C o n s t r u c t o r */ diff --git a/src/XrdOuc/XrdOucString.hh b/src/XrdOuc/XrdOucString.hh index b4f3c1f79d0..f463f3be87f 100644 --- a/src/XrdOuc/XrdOucString.hh +++ b/src/XrdOuc/XrdOucString.hh @@ -247,8 +247,6 @@ #include #include -using namespace std; - #define STR_NPOS -1 class XrdOucString { diff --git a/src/XrdSciTokens/vendor/picojson/examples/github-issues.cc b/src/XrdSciTokens/vendor/picojson/examples/github-issues.cc index 4bfb15b12ac..2ac93a2cba2 100644 --- a/src/XrdSciTokens/vendor/picojson/examples/github-issues.cc +++ b/src/XrdSciTokens/vendor/picojson/examples/github-issues.cc @@ -67,7 +67,6 @@ char *memfstrdup(MEMFILE *mf) { return buf; } -using namespace std; using namespace picojson; int main(int argc, char *argv[]) { diff --git a/src/XrdSciTokens/vendor/picojson/test.cc b/src/XrdSciTokens/vendor/picojson/test.cc index 582a29a259c..9cb2dce3336 100644 --- a/src/XrdSciTokens/vendor/picojson/test.cc +++ b/src/XrdSciTokens/vendor/picojson/test.cc @@ -32,7 +32,6 @@ #pragma warning(disable : 4127) // conditional expression is constant #endif -using namespace std; #define is(x, y, name) _ok((x) == (y), name) diff --git a/src/XrdSsi/XrdSsiLogging.cc b/src/XrdSsi/XrdSsiLogging.cc index 9241f58a1f9..0a8e127a424 100644 --- a/src/XrdSsi/XrdSsiLogging.cc +++ b/src/XrdSsi/XrdSsiLogging.cc @@ -51,7 +51,6 @@ namespace XrdSsi extern XrdSsiLogger::MCB_t *msgCB; } -using namespace std; using namespace XrdSsi; /******************************************************************************/ diff --git a/src/XrdSsi/XrdSsiShMam.cc b/src/XrdSsi/XrdSsiShMam.cc index fdd70e14309..be6f8e94022 100644 --- a/src/XrdSsi/XrdSsiShMam.cc +++ b/src/XrdSsi/XrdSsiShMam.cc @@ -44,7 +44,6 @@ #include "XrdSsi/XrdSsiShMam.hh" #include "XrdSys/XrdSysE2T.hh" -using namespace std; /* Gentoo removed OF from their copy of zconf.h but we need it here. See https://bugs.gentoo.org/show_bug.cgi?id=383179 for the sad history. diff --git a/src/XrdSys/XrdSysHeaders.hh b/src/XrdSys/XrdSysHeaders.hh index 88230fd5c65..62dcc23a9ad 100644 --- a/src/XrdSys/XrdSysHeaders.hh +++ b/src/XrdSys/XrdSysHeaders.hh @@ -38,7 +38,6 @@ // gcc >= 4.3, cl require this # include -using namespace std; #else diff --git a/src/XrdSys/XrdSysIOEventsPollE.icc b/src/XrdSys/XrdSysIOEventsPollE.icc index 12aa3d742fa..eb9cb8f296e 100644 --- a/src/XrdSys/XrdSysIOEventsPollE.icc +++ b/src/XrdSys/XrdSysIOEventsPollE.icc @@ -39,7 +39,6 @@ #define Atomic(x) x #endif -using namespace std; /******************************************************************************/ /* C l a s s P o l l E */ diff --git a/src/XrdSys/XrdSysIOEventsPollKQ.icc b/src/XrdSys/XrdSysIOEventsPollKQ.icc index abfffb7b0aa..584730b5f7a 100644 --- a/src/XrdSys/XrdSysIOEventsPollKQ.icc +++ b/src/XrdSys/XrdSysIOEventsPollKQ.icc @@ -40,7 +40,6 @@ #define Atomic(x) x #endif -using namespace std; /******************************************************************************/ /* C l a s s P o l l E */ diff --git a/src/XrdSys/XrdSysIOEventsPollPoll.icc b/src/XrdSys/XrdSysIOEventsPollPoll.icc index 9fe74720fc8..38172f27a85 100644 --- a/src/XrdSys/XrdSysIOEventsPollPoll.icc +++ b/src/XrdSys/XrdSysIOEventsPollPoll.icc @@ -37,7 +37,6 @@ #include "Xrd/XrdScheduler.hh" -using namespace std; /******************************************************************************/ /* C l a s s P o l l P o l l */ diff --git a/src/XrdSys/XrdSysIOEventsPollPort.icc b/src/XrdSys/XrdSysIOEventsPollPort.icc index d4059b5d989..f5a4ebd9388 100644 --- a/src/XrdSys/XrdSysIOEventsPollPort.icc +++ b/src/XrdSys/XrdSysIOEventsPollPort.icc @@ -35,7 +35,6 @@ @include "XrdSys/XrdSysE2T.hh" -using namespace std; /******************************************************************************/ /* C l a s s P o l l P o r t */ diff --git a/tests/XrdSsiTests/XrdShMap.cc b/tests/XrdSsiTests/XrdShMap.cc index 709ed8467f8..dfb51aae9d5 100644 --- a/tests/XrdSsiTests/XrdShMap.cc +++ b/tests/XrdSsiTests/XrdShMap.cc @@ -41,7 +41,6 @@ #include "XrdSsi/XrdSsiShMap.hh" -using namespace std; /* Gentoo removed OF from their copy of zconf.h but we need it here. See https://bugs.gentoo.org/show_bug.cgi?id=383179 for the sad history. From c071a30c73e56961828da79a58818dd2b961c136 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 14 Jun 2024 12:20:34 +0000 Subject: [PATCH 657/773] restrict new LB to section previously taken by load selection --- src/XrdCms/XrdCmsCluster.cc | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/XrdCms/XrdCmsCluster.cc b/src/XrdCms/XrdCmsCluster.cc index c5605c43565..5c81393a2ec 100644 --- a/src/XrdCms/XrdCmsCluster.cc +++ b/src/XrdCms/XrdCmsCluster.cc @@ -1785,7 +1785,7 @@ XrdCmsNode *XrdCmsCluster::SelbyCost(SMask_t mask, XrdCmsSelector &selR) XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) { XrdCmsNode *np, *sp = 0; -// bool Multi = False; + // bool Multi = false; bool reqSS = (selR.needSpace & XrdCmsNode::allowsSS) != 0; // Scan for a node (preset possible, suspended, overloaded, full, and dead) @@ -1813,11 +1813,11 @@ XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) || (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 (sp->RefW > (np->RefW+Config.DiskLinger)) sp=np;} - // else if (sp->myMass > np->myMass) sp=np; - // } else { + 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; + } else { // if (abs(sp->myLoad - np->myLoad) <= Config.P_fuzz) // {if (selR.selPack) // {if (--selR.selPack) sp=np; @@ -1830,13 +1830,16 @@ XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) // Multi = true; // } //add 1 to the inverse load, this is to allow some selection in case reported loads hit 100 - weighed[i] = selCap + 101 - np->myLoad; - selCap += 101 - np->myLoad; - - } - } + weighed[i] = selCap + 101 - np->myLoad; + selCap += 101 - np->myLoad; + } + // Multi = true; + } + } + } // pick a random weighed node // + if ( STHi > 1 ){ static std::random_device rand_dev; static std::mt19937 generator(rand_dev()); static std::uniform_int_distribution distr(randomSel,selCap); @@ -1847,6 +1850,7 @@ XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) break; } } + } delete [] weighed; // Check for overloaded node and return result From 99863390fe1cb3dcc894ec4af7b03215089cc195 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 14 Jun 2024 13:35:57 +0000 Subject: [PATCH 658/773] revert LB algorithm for rocky8 primary cmsd --- src/XrdCms/XrdCmsCluster.cc | 68 ++++++++++--------------------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/src/XrdCms/XrdCmsCluster.cc b/src/XrdCms/XrdCmsCluster.cc index 5c81393a2ec..3ade6d0880e 100644 --- a/src/XrdCms/XrdCmsCluster.cc +++ b/src/XrdCms/XrdCmsCluster.cc @@ -1785,24 +1785,12 @@ XrdCmsNode *XrdCmsCluster::SelbyCost(SMask_t mask, XrdCmsSelector &selR) XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) { XrdCmsNode *np, *sp = 0; - // bool Multi = false; - bool reqSS = (selR.needSpace & XrdCmsNode::allowsSS) != 0; + bool Multi = false, reqSS = (selR.needSpace & XrdCmsNode::allowsSS) != 0; // Scan for a node (preset possible, suspended, overloaded, full, and dead) // selR.Reset(); SelTcnt++; - int selCap = 1; - int randomSel=1; - int *weighed; - if (STHi > 0){ - weighed = new int[STHi]; - } - else{ - weighed = new int[1]; - } - for (int i = 0; i <= STHi; i++){ - //default 0 to skip the node in random selection if the below checks fail - weighed[i] = 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++; @@ -1818,45 +1806,23 @@ XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) {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 (--selR.selPack) sp=np; - // else break; - // } - // else if (sp->RefR > np->RefR) sp=np; - // } - // else if (sp->myLoad > np->myLoad) sp=np; - // } - // Multi = true; - // } - //add 1 to the inverse load, this is to allow some selection in case reported loads hit 100 - weighed[i] = selCap + 101 - np->myLoad; - selCap += 101 - np->myLoad; - } - // Multi = true; - } - } - } -// pick a random weighed node -// - if ( STHi > 1 ){ - static std::random_device rand_dev; - static std::mt19937 generator(rand_dev()); - static 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; + if (abs(sp->myLoad - np->myLoad) <= Config.P_fuzz) + {if (selR.selPack) + {if (--selR.selPack) sp=np; + else break; + } + 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); + RefCount(sp, Multi, selR.needSpace); return sp; } From d3d39999bc26bc49edc6cb0f2b43305d0a455805 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Fri, 10 Nov 2023 21:34:46 -0800 Subject: [PATCH 659/773] [Server] Add enhanced error message I/F and correct XrdOss:Create() arg passthrough issue This commit combines two previous commits from the 'devel' branch, listed below: - [Server] Pass through missing argument in XrdOss:Create() wrapper (a8f8f3df) - [Server] Commit remaining files/patches for inadvertent commit c29ef89 (3e73eb1b) --- src/XrdFfs/XrdFfsMisc.cc | 7 +- src/XrdOss/XrdOssWrapper.hh | 2 +- src/XrdOuc/XrdOucECMsg.cc | 175 ++++++++++++++++++++++++ src/XrdOuc/XrdOucECMsg.hh | 178 +++++++++++++++++++++++++ src/XrdPosix/XrdPosixAdmin.cc | 12 +- src/XrdPosix/XrdPosixAdmin.hh | 9 +- src/XrdPosix/XrdPosixDir.cc | 10 +- src/XrdPosix/XrdPosixDir.hh | 2 +- src/XrdPosix/XrdPosixExtra.cc | 3 + src/XrdPosix/XrdPosixFile.cc | 43 +++--- src/XrdPosix/XrdPosixFileRH.cc | 3 +- src/XrdPosix/XrdPosixMap.cc | 13 +- src/XrdPosix/XrdPosixMap.hh | 4 +- src/XrdPosix/XrdPosixObject.cc | 9 ++ src/XrdPosix/XrdPosixObject.hh | 7 +- src/XrdPosix/XrdPosixPrepIO.cc | 2 +- src/XrdPosix/XrdPosixXrootd.cc | 236 ++++++++++++++++++++------------- src/XrdPosix/XrdPosixXrootd.hh | 30 ++++- src/XrdPss/XrdPss.cc | 16 ++- src/XrdPss/XrdPss.hh | 2 + src/XrdUtils.cmake | 1 + 21 files changed, 616 insertions(+), 148 deletions(-) create mode 100644 src/XrdOuc/XrdOucECMsg.cc create mode 100644 src/XrdOuc/XrdOucECMsg.hh diff --git a/src/XrdFfs/XrdFfsMisc.cc b/src/XrdFfs/XrdFfsMisc.cc index f4ee7bdbba4..bc971719e83 100644 --- a/src/XrdFfs/XrdFfsMisc.cc +++ b/src/XrdFfs/XrdFfsMisc.cc @@ -44,6 +44,7 @@ #include "XrdNet/XrdNetAddr.hh" #include "XrdNet/XrdNetUtils.hh" +#include "XrdOuc/XrdOucECMsg.hh" #include "XrdPosix/XrdPosixAdmin.hh" #include "XrdSec/XrdSecEntity.hh" #include "XrdSecsss/XrdSecsssID.hh" @@ -75,7 +76,8 @@ char XrdFfsMisc_get_current_url(const char *oldurl, char *newurl) return 1; } - XrdPosixAdmin adm(oldurl); + XrdOucECMsg ecMsg; + XrdPosixAdmin adm(oldurl,ecMsg); if (adm.isOK() && adm.Stat()) { // We might have been redirected to a destination server. Better @@ -100,7 +102,8 @@ char* XrdFfsMisc_getNameByAddr(char* ipaddr) int XrdFfsMisc_get_all_urls_real(const char *oldurl, char **newurls, const int nnodes) { - XrdPosixAdmin adm(oldurl); + XrdOucECMsg ecMsg; + XrdPosixAdmin adm(oldurl,ecMsg); XrdCl::URL *uVec; int i, rval = 0; diff --git a/src/XrdOss/XrdOssWrapper.hh b/src/XrdOss/XrdOssWrapper.hh index 4df3d792330..8658271293f 100644 --- a/src/XrdOss/XrdOssWrapper.hh +++ b/src/XrdOss/XrdOssWrapper.hh @@ -509,7 +509,7 @@ virtual void Connect(XrdOucEnv &env) {wrapPI.Connect(env);} virtual int Create(const char *tid, const char *path, mode_t mode, XrdOucEnv &env, int opts=0) - {return wrapPI.Create(tid, path, mode, env);} + {return wrapPI.Create(tid, path, mode, env, opts);} //----------------------------------------------------------------------------- //! Notify storage system that a client has disconnected. diff --git a/src/XrdOuc/XrdOucECMsg.cc b/src/XrdOuc/XrdOucECMsg.cc new file mode 100644 index 00000000000..6189d40b022 --- /dev/null +++ b/src/XrdOuc/XrdOucECMsg.cc @@ -0,0 +1,175 @@ +/******************************************************************************/ +/* */ +/* X r d O u c E C M s g . c c */ +/* */ +/* (c) 2023 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/XrdOucECMsg.hh" +#include "XrdSys/XrdSysE2T.hh" + +/******************************************************************************/ +/* G e t */ +/******************************************************************************/ + +int XrdOucECMsg::Get(std::string& ecm, bool rst) +{ + if (!rst) + {ecm = ecMsg; + return eCode; + } + + int ec = eCode; + eCode = 0; + ecm = std::move(ecMsg); + ecMsg.erase(); + return ec; +} + +/******************************************************************************/ +/* M s g */ +/******************************************************************************/ + +void XrdOucECMsg::Msg(const char *pfx, const char *txt1, + const char *txt2, const char *txt3, + const char *txt4, const char *txt5) +{ + + const char *vecP[10]; + int n = 0; + bool xSpace = false; + + if (txt1 && *txt1) {vecP[n++] = txt1; xSpace = true;} + if (txt2 && *txt2) {if (xSpace) vecP[n++] = " "; + vecP[n++] = txt2; xSpace = true; + } + if (txt3 && *txt3) {if (xSpace) vecP[n++] = " "; + vecP[n++] = txt3; xSpace = true; + } + if (txt4 && *txt4) {if (xSpace) vecP[n++] = " "; + vecP[n++] = txt4; xSpace = true; + } + if (txt5 && *txt5) {if (xSpace) vecP[n++] = " "; + vecP[n++] = txt5; + } + +// Route the message appropriately +// + MsgVec(pfx, vecP, n); +} + +/******************************************************************************/ +/* M s g f */ +/******************************************************************************/ + +void XrdOucECMsg::Msgf(const char *pfx, const char *fmt, ...) +{ + char buffer[2048]; + va_list args; + va_start (args, fmt); + +// Format the message +// + int n = vsnprintf(buffer, sizeof(buffer), fmt, args); + +// Append as needed +// + if (n > (int)sizeof(buffer)) n = sizeof(buffer); + Setup(pfx, n); + ecMsg.append(buffer); +} + +/******************************************************************************/ +/* M s g V A */ +/******************************************************************************/ + +void XrdOucECMsg::MsgVA(const char *pfx, const char *fmt, va_list aP) +{ + char buffer[2048]; + +// Format the message +// + int n = vsnprintf(buffer, sizeof(buffer), fmt, aP); + +// Append as needed +// + if (n > (int)sizeof(buffer)) n = sizeof(buffer); + Setup(pfx, n); + ecMsg.append(buffer); +} + +/******************************************************************************/ +/* M s g V e c */ +/******************************************************************************/ + +void XrdOucECMsg::MsgVec(const char* pfx, char const* const* vecP, int vecN) +{ + int n = 0; + + for (int i = 0; i < vecN; i++) n += strlen(vecP[i]); + Setup(pfx, n); + for (int i = 0; i < vecN; i++) ecMsg.append(vecP[i]); +} + +/******************************************************************************/ +/* S e t E r r n o */ +/******************************************************************************/ + +int XrdOucECMsg::SetErrno(int ecc, int ret, const char *alt) +{ + if (!alt || *alt != '*') + {if (!msgID) ecMsg = (alt ? alt : XrdSysE2T(ecc)); + else Msgf(msgID, XrdSysE2T(ecc)); + } + errno = eCode = ecc; + return ret; +} + +/******************************************************************************/ +/* S e t u p */ +/******************************************************************************/ + +void XrdOucECMsg::Setup(const char* pfx, int n) +{ + int k = (pfx && *pfx ? strlen(pfx)+2 : 0); + + if (Delim) + {ecMsg.reserve(ecMsg.length() + n + k + 2); + ecMsg.append(&Delim, 1); + Delim = 0; + } else { + ecMsg.reserve(n + k + 1); + ecMsg = ""; + } + + if (k) + {ecMsg.append(pfx); + ecMsg.append(": "); + } +} diff --git a/src/XrdOuc/XrdOucECMsg.hh b/src/XrdOuc/XrdOucECMsg.hh new file mode 100644 index 00000000000..b29c40e78d0 --- /dev/null +++ b/src/XrdOuc/XrdOucECMsg.hh @@ -0,0 +1,178 @@ +#ifndef __OUC_ECMSG_H__ +#define __OUC_ECMSG_H__ +/******************************************************************************/ +/* */ +/* X r d O u c E C M s g . h h */ +/* */ +/* (c) 2023 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 XrdOucECMsg +{ +public: + +//----------------------------------------------------------------------------- +//! Append subsequent message to contents of ecMsg using delimeter. Append +//! allows method chaining for a more natural progamming style. +//! +//! @param dlm !0 -> The character to use as message separator. +//! dlm =0 -> Turns off appending, next message replaces ecMsg. +//! +//! @return Reference to the subject object. +//----------------------------------------------------------------------------- + +XrdOucECMsg& Append(char dlm='\n') {Delim = dlm; return *this;} + +//----------------------------------------------------------------------------- +//! Get err code and error message. +//! +//! @param ecm Reference to std:string where message is to be placed. +//! @param rst When true, the eCode and internal string are set to null. +//! +//! @return the error code, eCode, and associated error message. +//----------------------------------------------------------------------------- + +int Get(std::string& ecm, bool rst=true); + +//----------------------------------------------------------------------------- +//! Determine if an error text message exists. +//! +//! @return true if an error text message exists, false otherwise. +//----------------------------------------------------------------------------- + +bool hasMsg() const {return !ecMsg.empty();} + +//----------------------------------------------------------------------------- +//! Return the message string. +//! +//! @return A reference to the message string. +//----------------------------------------------------------------------------- + +std::string& Msg() {return ecMsg;} + +//----------------------------------------------------------------------------- +//! Insert a space delimited error message into ecMsg string. +//! +//! @param pfx !0 -> the text to prefix the message; the message is formed as +//! pfx: txt1 [txt2] [txt3] +//! pfx =0 -> insert message into ecMsg without a prefix. +//! @param txt1,txt2,txt3,txt4,txt5 the message to be inserted to ecMsg. +//----------------------------------------------------------------------------- + +void Msg(const char* pfx, const char* txt1, + const char* txt2=0, const char* txt3=0, + const char* txt4=0, const char* txt5=0); + +//----------------------------------------------------------------------------- +//! Insert a formated error message into ecMsg using variable args. +//! +//! @param pfx !0 -> the text to prefix the message; the message is formed as +//! : +//! pfx =0 -> insert message without a prefix. +//! @param fmt the message formatting template (i.e. sprintf format). +//! @param ... the arguments that should be used with the template. The +//! formatted message is truncated at 2048 bytes. +//----------------------------------------------------------------------------- + +void Msgf(const char *pfx, const char *fmt, ...); + +//----------------------------------------------------------------------------- +//! Insert a formated error message into the ecMsg using a va_list. +//! +//! @param pfx !0 -> the text to prefix the message; the message is formed as +//! : +//! pfx =0 -> add message to the log without a prefix. +//! @param fmt the message formatting template (i.e. sprintf format). +//! @param aP the arguments that should be used with the template. The +//! formatted message is truncated at 2048 bytes. +//----------------------------------------------------------------------------- + +void MsgVA(const char *pfx, const char *fmt, std::va_list aP); + +//----------------------------------------------------------------------------- +//! Insert a formated error message into ecMsg using an iovec. +//! +//! @param pfx !0 -> the text to prefix the message; the message is formed as +//! pfx: +//! pfx =0 -> insert message into ecMsg without a prefix. +//! @param vecP pointer to a vector strings to insert into the message. +//! Spaces are not inserted between the elements. +//! @param vecN the number of elements in vecP. +//----------------------------------------------------------------------------- + +void MsgVec(const char* pfx, char const* const* vecP, int vecN); + +//----------------------------------------------------------------------------- +//! Set error message and error code. +//! +//! @param ecc The error code. +//! @param ecm The error message, if nil then message is not changed. +//----------------------------------------------------------------------------- + +void Set(int ecc, const char* ecm="") {eCode = ecc; if (ecm) ecMsg = ecm;} + +void Set(int ecc, std::string& ecm) {eCode = ecc; ecMsg = ecm;} + +//----------------------------------------------------------------------------- +//! Set default error message, error code, and errno. +//! +//! @param ecc The error code. +//! @param ret The value to be returned, default -1. +//! @param alt Alternative message, default text of ecc error. +//! +//! @return The ret parameter value is returned. +//----------------------------------------------------------------------------- + +int SetErrno(int ecc, int retval=-1, const char *alt=0); + +//----------------------------------------------------------------------------- +//! Assignment operators for convenience. +//----------------------------------------------------------------------------- + + XrdOucECMsg& operator=(const int rhs) {eCode = rhs; return *this;}; + + XrdOucECMsg& operator=(const std::string& rhs) {ecMsg = rhs; return *this;}; + + XrdOucECMsg& operator=(const char* rhs) {ecMsg = rhs; return *this;}; + + XrdOucECMsg& operator=(XrdOucECMsg& rhs) + {ecMsg = rhs.ecMsg; eCode = rhs.eCode; return *this;}; + + XrdOucECMsg(const char *msgid=0) : msgID(msgid) {} + ~XrdOucECMsg() {} + +private: + +void Setup(const char *pfx, int n); +const char* msgID; +std::string ecMsg; +int eCode = 0; +char Delim = 0; +}; +#endif diff --git a/src/XrdPosix/XrdPosixAdmin.cc b/src/XrdPosix/XrdPosixAdmin.cc index 5e37e72a267..f03f29e295e 100644 --- a/src/XrdPosix/XrdPosixAdmin.cc +++ b/src/XrdPosix/XrdPosixAdmin.cc @@ -61,7 +61,7 @@ XrdCl::URL *XrdPosixAdmin::FanOut(int &num) // xStatus = Xrd.DeepLocate(Url.GetPathWithParams(),XrdCl::OpenFlags::None,info); if (!xStatus.IsOK()) - {num = XrdPosixMap::Result(xStatus, false); + {num = XrdPosixMap::Result(xStatus, ecMsg, false); return 0; } @@ -110,7 +110,7 @@ int XrdPosixAdmin::Query(XrdCl::QueryCode::Code reqCode, void *buff, int bsz) // Issue the query // - if (!XrdPosixMap::Result(Xrd.Query(reqCode, reqBuff, rspBuff))) + if (!XrdPosixMap::Result(Xrd.Query(reqCode, reqBuff, rspBuff),ecMsg)) {uint32_t rspSz = rspBuff->GetSize(); char *rspbuff = rspBuff->GetBuffer(); if (rspbuff && rspSz) @@ -121,8 +121,8 @@ int XrdPosixAdmin::Query(XrdCl::QueryCode::Code reqCode, void *buff, int bsz) ((char*)buff)[rspSz] = 0; // make sure it is null-terminated delete rspBuff; return static_cast(rspSz + 1); - } else errno = ERANGE; - } else errno = EFAULT; + } else ecMsg.SetErrno(ERANGE,0,"buffer to small to hold result"); + } else ecMsg.SetErrno(EFAULT,0,"Invalid return results"); } // Return error @@ -148,7 +148,7 @@ bool XrdPosixAdmin::Stat(mode_t *flags, time_t *mtime) // xStatus = Xrd.Stat(Url.GetPathWithParams(), sInfo); if (!xStatus.IsOK()) - {XrdPosixMap::Result(xStatus); + {XrdPosixMap::Result(xStatus,ecMsg); delete sInfo; return false; } @@ -179,7 +179,7 @@ bool XrdPosixAdmin::Stat(struct stat &Stat) // xStatus = Xrd.Stat(Url.GetPathWithParams(), sInfo); if (!xStatus.IsOK()) - {XrdPosixMap::Result(xStatus); + {XrdPosixMap::Result(xStatus,ecMsg); delete sInfo; return false; } diff --git a/src/XrdPosix/XrdPosixAdmin.hh b/src/XrdPosix/XrdPosixAdmin.hh index 1ced9fda4ac..f56ce54c1bf 100644 --- a/src/XrdPosix/XrdPosixAdmin.hh +++ b/src/XrdPosix/XrdPosixAdmin.hh @@ -36,6 +36,7 @@ #include "XrdCl/XrdClFileSystem.hh" #include "XrdCl/XrdClURL.hh" #include "XrdCl/XrdClXRootDResponses.hh" +#include "XrdOuc/XrdOucECMsg.hh" /******************************************************************************/ /* X r d P o s i x A d m i n */ @@ -49,8 +50,12 @@ public: XrdCl::URL Url; XrdCl::FileSystem Xrd; +XrdOucECMsg& ecMsg; bool isOK() {if (Url.IsValid()) return true; + ecMsg.Set(EINVAL, 0); + ecMsg.Msgf("PosixAdmin", "url '%s' is invalid", + Url.GetURL()); errno = EINVAL; return false; } @@ -62,8 +67,8 @@ bool Stat(mode_t *flags=0, time_t *mtime=0); bool Stat(struct stat &Stat); - XrdPosixAdmin(const char *path) - : Url((std::string)path), Xrd(Url) {} + XrdPosixAdmin(const char *path, XrdOucECMsg &ecm) + : Url((std::string)path), Xrd(Url), ecMsg(ecm) {} ~XrdPosixAdmin() {} }; #endif diff --git a/src/XrdPosix/XrdPosixDir.cc b/src/XrdPosix/XrdPosixDir.cc index 027d670aca0..8d55e34ac77 100644 --- a/src/XrdPosix/XrdPosixDir.cc +++ b/src/XrdPosix/XrdPosixDir.cc @@ -54,7 +54,7 @@ dirent64 *XrdPosixDir::nextEntry(dirent64 *dp) // Reread the directory if we need to (rewind forces this) // - if (!myDirVec && !Open()) {eNum = errno; return 0;} + if (!myDirVec && !Open()) {eNum = errno; return 0;} // Open() sets ecMsg // Check if dir is empty or all entries have been read // @@ -100,15 +100,17 @@ DIR *XrdPosixDir::Open() // some system the dirent structure does not include the name buffer // if (!myDirEnt && !(myDirEnt = (dirent64 *)malloc(dEntSize))) - {errno = ENOMEM; return (DIR *)0;} + {ecMsg.SetErrno(ENOMEM); + return (DIR*)0; + } // Get the directory list // rc = XrdPosixMap::Result(DAdmin.Xrd.DirList(DAdmin.Url.GetPathWithParams(), XrdPosixGlobals::dlFlag, - myDirVec, (uint16_t)0)); + myDirVec, (uint16_t)0),ecMsg); -// If we failed, return a zero pointer +// If we failed, return a zero pointer ote that Result() set errno for us // if (rc) return (DIR *)0; diff --git a/src/XrdPosix/XrdPosixDir.hh b/src/XrdPosix/XrdPosixDir.hh index c7985af7894..be1daee555a 100644 --- a/src/XrdPosix/XrdPosixDir.hh +++ b/src/XrdPosix/XrdPosixDir.hh @@ -49,7 +49,7 @@ class XrdPosixDir : public XrdPosixObject { public: XrdPosixDir(const char *path) - : DAdmin(path), myDirVec(0), myDirEnt(0), + : DAdmin(path,ecMsg), myDirVec(0), myDirEnt(0), nxtEnt(0), numEnt(0), eNum(0) {} diff --git a/src/XrdPosix/XrdPosixExtra.cc b/src/XrdPosix/XrdPosixExtra.cc index a77b3e27c3a..307f1733740 100644 --- a/src/XrdPosix/XrdPosixExtra.cc +++ b/src/XrdPosix/XrdPosixExtra.cc @@ -37,6 +37,8 @@ #include "XrdPosix/XrdPosixExtra.hh" #include "XrdPosix/XrdPosixFile.hh" + + /******************************************************************************/ /* p g R e a d */ /******************************************************************************/ @@ -64,6 +66,7 @@ ssize_t XrdPosixExtra::pgRead (int fildes, void* buffer, // if (rdlen > (size_t)0x7fffffff) {fp->UnLock(); + errno = EOVERFLOW; if (!cbp) return -1; cbp->Complete(-1); diff --git a/src/XrdPosix/XrdPosixFile.cc b/src/XrdPosix/XrdPosixFile.cc index 5a58f7b2f05..09c55f971e3 100644 --- a/src/XrdPosix/XrdPosixFile.cc +++ b/src/XrdPosix/XrdPosixFile.cc @@ -400,11 +400,12 @@ void XrdPosixFile::HandleResponse(XrdCl::XRootDStatus *status, // If no errors occurred, complete the open // - if (!(status->IsOK())) rc = XrdPosixMap::Result(*status); - else if (!Finalize(&Status)) rc = XrdPosixMap::Result(Status); + if (!(status->IsOK())) rc = XrdPosixMap::Result(*status,ecMsg,false); + else if (!Finalize(&Status)) rc = XrdPosixMap::Result( Status,ecMsg,false); -// Issue XrdPosixCallBack callback with the correct result. Note: errors are -// indicated with result set to -1 and errno set to the error number. +// Issue XrdPosixCallBack callback with the correct result. Errors are indicated +// by result set < 0 (typically -1) and errno set to the error number. In our +// case, rc is -errno if an error occured and that is what the callback gets. // xeqCB->Complete(rc); @@ -489,10 +490,10 @@ void XrdPosixFile::pgRead(XrdOucCacheIOCB &iocb, Ref(); Status = clFile.PgRead((uint64_t)offs,(uint32_t)rlen,buff,rhP); -// Check status +// Check status, upon error we pass -errno as the result. // if (!Status.IsOK()) - {rhP->Sched(XrdPosixMap::Result(Status, false)); + {rhP->Sched(XrdPosixMap::Result(Status, ecMsg, false)); unRef(); } } @@ -514,13 +515,13 @@ int XrdPosixFile::pgWrite(char *buff, // if (csfix) *csfix = 0; -// Issue write and return appropriately +// Issue write and return appropriately. An error returns -1. // Ref(); Status = clFile.PgWrite((uint64_t)offs, (uint32_t)wlen, buff, csvec); unRef(); - return (Status.IsOK() ? wlen : XrdPosixMap::Result(Status)); + return (Status.IsOK() ? wlen : XrdPosixMap::Result(Status,ecMsg,true)); } /******************************************************************************/ @@ -553,10 +554,10 @@ void XrdPosixFile::pgWrite(XrdOucCacheIOCB &iocb, Ref(); Status = clFile.PgWrite((uint64_t)offs, (uint32_t)wlen, buff, csvec, rhP); -// Check status +// Check status, if error pass along -errno as the result. // if (!Status.IsOK()) - {rhP->Sched(XrdPosixMap::Result(Status)); + {rhP->Sched(XrdPosixMap::Result(Status,ecMsg,false)); unRef(); } } @@ -584,7 +585,7 @@ int XrdPosixFile::Read (char *Buff, long long Offs, int Len) Status = clFile.Read((uint64_t)Offs, (uint32_t)Len, Buff, bytes); unRef(); - return (Status.IsOK() ? (int)bytes : XrdPosixMap::Result(Status, false)); + return (Status.IsOK() ? (int)bytes : XrdPosixMap::Result(Status,ecMsg,false)); } /******************************************************************************/ @@ -608,10 +609,10 @@ void XrdPosixFile::Read (XrdOucCacheIOCB &iocb, char *buff, long long offs, if (doPgRd) Status = clFile.PgRead((uint64_t)offs,(uint32_t)rlen,buff,rhP); else Status = clFile.Read ((uint64_t)offs,(uint32_t)rlen,buff,rhP); -// Check status +// Check status. Upon error pass along -errno as the result. // if (!Status.IsOK()) - {rhP->Sched(XrdPosixMap::Result(Status, false)); + {rhP->Sched(XrdPosixMap::Result(Status, ecMsg, false)); unRef(); } } @@ -646,9 +647,9 @@ int XrdPosixFile::ReadV (const XrdOucIOVec *readV, int n) unRef(); delete vrInfo; -// Return appropriate result +// Return appropriate result (here we return -errno as the result) // - return (Status.IsOK() ? nbytes : XrdPosixMap::Result(Status, false)); + return (Status.IsOK() ? nbytes : XrdPosixMap::Result(Status, ecMsg, false)); } /******************************************************************************/ @@ -680,7 +681,7 @@ void XrdPosixFile::ReadV(XrdOucCacheIOCB &iocb, const XrdOucIOVec *readV, int n) // Return appropriate result // if (!Status.IsOK()) - {rhp->Sched(XrdPosixMap::Result(Status, false)); + {rhp->Sched(XrdPosixMap::Result(Status, ecMsg, false)); unRef(); } } @@ -744,7 +745,7 @@ int XrdPosixFile::Sync() // Return result // - return XrdPosixMap::Result(Status, false); + return XrdPosixMap::Result(Status, ecMsg, false); } /******************************************************************************/ @@ -761,7 +762,7 @@ void XrdPosixFile::Sync(XrdOucCacheIOCB &iocb) // Check status // - if (!Status.IsOK()) rhp->Sched(XrdPosixMap::Result(Status, false)); + if (!Status.IsOK()) rhp->Sched(XrdPosixMap::Result(Status, ecMsg, false)); } /******************************************************************************/ @@ -780,7 +781,7 @@ int XrdPosixFile::Trunc(long long Offset) // Return results // - return XrdPosixMap::Result(Status); + return XrdPosixMap::Result(Status,ecMsg,false); } /******************************************************************************/ @@ -797,7 +798,7 @@ int XrdPosixFile::Write(char *Buff, long long Offs, int Len) Status = clFile.Write((uint64_t)Offs, (uint32_t)Len, Buff); unRef(); - return (Status.IsOK() ? Len : XrdPosixMap::Result(Status)); + return (Status.IsOK() ? Len : XrdPosixMap::Result(Status,ecMsg,false)); } /******************************************************************************/ @@ -817,7 +818,7 @@ void XrdPosixFile::Write(XrdOucCacheIOCB &iocb, char *buff, long long offs, // Check status // if (!Status.IsOK()) - {rhp->Sched(XrdPosixMap::Result(Status)); + {rhp->Sched(XrdPosixMap::Result(Status,ecMsg,false)); unRef(); } } diff --git a/src/XrdPosix/XrdPosixFileRH.cc b/src/XrdPosix/XrdPosixFileRH.cc index 47ab3c0a298..a892f807299 100644 --- a/src/XrdPosix/XrdPosixFileRH.cc +++ b/src/XrdPosix/XrdPosixFileRH.cc @@ -111,7 +111,8 @@ void XrdPosixFileRH::HandleResponse(XrdCl::XRootDStatus *status, // Determine ending status. Note: error indicated as result set to -errno. // - if (!(status->IsOK())) result = XrdPosixMap::Result(*status, false); + if (!(status->IsOK())) + result = XrdPosixMap::Result(*status, theFile->ecMsg, false); else if (typeIO == nonIO) result = 0; else if (typeIO == isRead) {XrdCl::ChunkInfo *cInfo = 0; diff --git a/src/XrdPosix/XrdPosixMap.cc b/src/XrdPosix/XrdPosixMap.cc index 7796c926e50..ec8caed292e 100644 --- a/src/XrdPosix/XrdPosixMap.cc +++ b/src/XrdPosix/XrdPosixMap.cc @@ -31,6 +31,7 @@ #include #include +#include "XrdOuc/XrdOucECMsg.hh" #include "XProtocol/XProtocol.hh" #include "XrdPosix/XrdPosixMap.hh" #include "XrdSfs/XrdSfsFlags.hh" @@ -146,9 +147,9 @@ XrdCl::Access::Mode XrdPosixMap::Mode2Access(mode_t mode) /* R e s u l t */ /******************************************************************************/ -int XrdPosixMap::Result(const XrdCl::XRootDStatus &Status, bool retneg1) +int XrdPosixMap::Result(const XrdCl::XRootDStatus &Status, + XrdOucECMsg& ecMsg, bool retneg1) { - std::string eText; int eNum; // If all went well, return success @@ -158,21 +159,21 @@ int XrdPosixMap::Result(const XrdCl::XRootDStatus &Status, bool retneg1) // If this is an xrootd error then get the xrootd generated error // if (Status.code == XrdCl::errErrorResponse) - {eText = Status.GetErrorMessage(); + {ecMsg = Status.GetErrorMessage(); eNum = XProtocol::toErrno(Status.errNo); } else { - eText = Status.ToStr(); + ecMsg = Status.ToStr(); eNum = (Status.errNo ? Status.errNo : mapCode(Status.code)); } // Trace this if need be (we supress this for as we really need more info to // make this messae useful like the opteration and path). // -// if (eNum != ENOENT && !eText.empty() && Debug) +// if (eNum != ENOENT && !ecMsg.hasMsg() && Debug) // std::cerr <<"XrdPosix: " < #include +class XrdOucECMsg; + class XrdPosixMap { public: @@ -46,7 +48,7 @@ static mode_t Flags2Mode(dev_t *rdv, uint32_t flags); static XrdCl::Access::Mode Mode2Access(mode_t mode); static int Result(const XrdCl::XRootDStatus &Status, - bool retneg1=false); + XrdOucECMsg& ecMsg, bool retneg1=false); static void SetDebug(bool dbg) {Debug = dbg;} diff --git a/src/XrdPosix/XrdPosixObject.cc b/src/XrdPosix/XrdPosixObject.cc index 2c986c333e5..3e054d0e64b 100644 --- a/src/XrdPosix/XrdPosixObject.cc +++ b/src/XrdPosix/XrdPosixObject.cc @@ -38,6 +38,15 @@ #include "XrdSys/XrdSysHeaders.hh" #include "XrdSys/XrdSysTimer.hh" +/******************************************************************************/ +/* G l o b a l s */ +/******************************************************************************/ + +namespace XrdPosixGlobals +{ +extern thread_local XrdOucECMsg ecMsg; +} + /******************************************************************************/ /* S t a t i c M e m b e r s */ /******************************************************************************/ diff --git a/src/XrdPosix/XrdPosixObject.hh b/src/XrdPosix/XrdPosixObject.hh index 0b7b5ebc7b1..578a0d6ed55 100644 --- a/src/XrdPosix/XrdPosixObject.hh +++ b/src/XrdPosix/XrdPosixObject.hh @@ -32,6 +32,7 @@ #include +#include "XrdOuc/XrdOucECMsg.hh" #include "XrdSys/XrdSysAtomics.hh" #include "XrdSys/XrdSysPthread.hh" @@ -52,6 +53,8 @@ static XrdPosixFile *File(int fildes, bool glk=false); int FDNum() {return fdNum;} + XrdOucECMsg* getECMsg() {return &ecMsg;} + static int Init(int numfd); void Lock(bool wr=true) @@ -87,9 +90,11 @@ virtual bool Who(XrdPosixDir **dirP) {return false;} virtual bool Who(XrdPosixFile **fileP) {return false;} - XrdPosixObject() : fdNum(-1), refCnt(0) {} + XrdPosixObject() : ecMsg("[posix]"),fdNum(-1),refCnt(0) {} virtual ~XrdPosixObject() {if (fdNum >= 0) Release(this);} + XrdOucECMsg ecMsg; + protected: XrdSysRecMutex updMutex; XrdSysRWLock objMutex; diff --git a/src/XrdPosix/XrdPosixPrepIO.cc b/src/XrdPosix/XrdPosixPrepIO.cc index 27846075b57..47c80ed4b25 100644 --- a/src/XrdPosix/XrdPosixPrepIO.cc +++ b/src/XrdPosix/XrdPosixPrepIO.cc @@ -99,7 +99,7 @@ bool XrdPosixPrepIO::Init(XrdOucCacheIOCB *iocbP) // Make sure all went well. If so, do a Stat() call on the underlying file // if (Status.IsOK()) fileP->Stat(Status); - else {openRC = XrdPosixMap::Result(Status, false); + else {openRC = XrdPosixMap::Result(Status, fileP->ecMsg, false); if (DEBUGON && errno != ENOENT && errno != ELOOP) {std::string eTxt = Status.ToString(); DEBUG(eTxt<<" deferred open "<Origin()); diff --git a/src/XrdPosix/XrdPosixXrootd.cc b/src/XrdPosix/XrdPosixXrootd.cc index 64494207bc6..bfd28557889 100644 --- a/src/XrdPosix/XrdPosixXrootd.cc +++ b/src/XrdPosix/XrdPosixXrootd.cc @@ -51,6 +51,7 @@ #include "XrdSys/XrdSysPlatform.hh" #include "XrdOuc/XrdOucCache.hh" +#include "XrdOuc/XrdOucECMsg.hh" #include "XrdOuc/XrdOucEnv.hh" #include "XrdOuc/XrdOucName2Name.hh" #include "XrdOuc/XrdOucPsx.hh" @@ -79,6 +80,8 @@ class XrdSysError; namespace XrdPosixGlobals { +thread_local XrdOucECMsg ecMsg("[posix]"); + XrdScheduler *schedP = 0; XrdOucCache *theCache = 0; XrdOucName2Name *theN2N = 0; @@ -147,10 +150,7 @@ int OpenDefer(XrdPosixFile *fp, // Assign a file descriptor to this file // if (!(fp->AssignFD(isStream))) - {delete fp; - errno = EMFILE; - return -1; - } + {delete fp; return XrdPosixGlobals::ecMsg.SetErrno(EMFILE);} // Allocate a prepare I/O object to defer this open // @@ -240,7 +240,7 @@ XrdPosixXrootd::~XrdPosixXrootd() int XrdPosixXrootd::Access(const char *path, int amode) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); mode_t stMode; bool aOK = true; @@ -257,8 +257,7 @@ int XrdPosixXrootd::Access(const char *path, int amode) // All done // if (aOK) return 0; - errno = EACCES; - return -1; + return XrdPosixGlobals::ecMsg.SetErrno(EACCES); } /******************************************************************************/ @@ -276,7 +275,7 @@ int XrdPosixXrootd::Close(int fildes) // number so no one can reference this file again. // if (!(fP = XrdPosixObject::ReleaseFile(fildes))) - {errno = EBADF; return -1;} + return XrdPosixGlobals::ecMsg.SetErrno(EBADF); // Detach the file from a possible cache. We need to up the reference count // to synchrnoize with any possible callback as we may need to place this @@ -299,9 +298,9 @@ int XrdPosixXrootd::Close(int fildes) // if (fP) XrdPosixFile::DelayedDestroy(fP); -// Return final result +// Return final result. Note: close errors are recorded in global thread status // - return (ret ? 0 : XrdPosixMap::Result(Status)); + return (ret ? 0 : XrdPosixMap::Result(Status,XrdPosixGlobals::ecMsg,true)); } /******************************************************************************/ @@ -316,7 +315,7 @@ int XrdPosixXrootd::Closedir(DIR *dirp) // Get the directory object // if (!(dP = XrdPosixObject::ReleaseDir(fildes))) - {errno = EBADF; return -1;} + return XrdPosixGlobals::ecMsg.SetErrno(EBADF); // Deallocate the directory // @@ -385,7 +384,7 @@ int XrdPosixXrootd::Fstat(int fildes, struct stat *buf) if (rc <= 0) {fp->UnLock(); if (!rc) return 0; - errno = -rc; + errno = -rc; //??? return -1; } @@ -464,7 +463,7 @@ int XrdPosixXrootd::Ftruncate(int fildes, off_t offset) long long XrdPosixXrootd::Getxattr (const char *path, const char *name, void *value, unsigned long long size) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); XrdCl::QueryCode::Code reqCode; int vsize = static_cast(size); @@ -478,7 +477,7 @@ long long XrdPosixXrootd::Getxattr (const char *path, const char *name, { if (!strcmp(name,"xroot.cksum")) reqCode=XrdCl::QueryCode::Checksum; else if (!strcmp(name,"xroot.space")) reqCode=XrdCl::QueryCode::Space; else if (!strcmp(name,"xroot.xattr")) reqCode=XrdCl::QueryCode::XAttr; - else {errno = ENOATTR; return -1;} + else {errno = ENOATTR; return -1;} //??? }else {errno = EINVAL; return -1;} // Stat the file first to allow vectoring of the request to the right server @@ -527,7 +526,7 @@ off_t XrdPosixXrootd::Lseek(int fildes, off_t offset, int whence) int XrdPosixXrootd::Mkdir(const char *path, mode_t mode) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); XrdCl::MkDirFlags::Flags flags; // Preferentially make the whole path unless told otherwise @@ -543,7 +542,8 @@ int XrdPosixXrootd::Mkdir(const char *path, mode_t mode) // return XrdPosixMap::Result(admin.Xrd.MkDir(admin.Url.GetPathWithParams(), flags, - XrdPosixMap::Mode2Access(mode)) + XrdPosixMap::Mode2Access(mode)), + XrdPosixGlobals::ecMsg,true ); } @@ -583,7 +583,7 @@ int XrdPosixXrootd::Open(const char *path, int oflags, mode_t mode, // if (oflags & isStream) {if (XrdPosixObject::CanStream()) Opts |= XrdPosixFile::isStrm; - else {errno = EMFILE; return -1;} + return XrdPosixGlobals::ecMsg.SetErrno(EMFILE); } // Translate create vs simple open. Always make dirpath on create! @@ -600,9 +600,7 @@ int XrdPosixXrootd::Open(const char *path, int oflags, mode_t mode, // Allocate the new file object // if (!(fp = new XrdPosixFile(aOK, path, cbP, Opts))) - {errno = EMFILE; - return -1; - } + return XrdPosixGlobals::ecMsg.SetErrno(EMFILE); // Check if all went well during allocation // @@ -620,7 +618,7 @@ int XrdPosixXrootd::Open(const char *path, int oflags, mode_t mode, } rc = XrdPosixGlobals::theCache->Prepare(fp->Path(), oflags, mode); if (rc > 0) return OpenDefer(fp, cbP, XOflags, XOmode, oflags&isStream); - if (rc < 0) {delete fp; errno = -rc; return -1;} + if (rc < 0) {delete fp; return XrdPosixGlobals::ecMsg.SetErrno(-rc);} } // Open the file (sync or async) @@ -634,14 +632,11 @@ int XrdPosixXrootd::Open(const char *path, int oflags, mode_t mode, // if (!Status.IsOK()) {XrdPosixGlobals::Stats.Count(XrdPosixGlobals::Stats.X.OpenErrs); - XrdPosixMap::Result(Status); - int rc = errno; - if (DEBUGON && rc != ENOENT && rc != ELOOP) - {std::string eTxt = Status.ToString(); - DEBUG(eTxt <<" open " <Origin()); - } + int rc = XrdPosixMap::Result(Status,XrdPosixGlobals::ecMsg,false); + if (DEBUGON && rc != -ENOENT && rc != -ELOOP) + {DEBUG(XrdPosixGlobals::ecMsg.Msg() <<" open " <Origin());} delete fp; - errno = rc; + errno = -rc; // Saved errno across the delete return -1; } @@ -649,8 +644,7 @@ int XrdPosixXrootd::Open(const char *path, int oflags, mode_t mode, // if (!(fp->AssignFD(oflags & isStream))) {delete fp; - errno = EMFILE; - return -1; + return XrdPosixGlobals::ecMsg.SetErrno(EMFILE); } // Finalize the open (this gets the stat info). For async opens, the @@ -658,7 +652,7 @@ int XrdPosixXrootd::Open(const char *path, int oflags, mode_t mode, // if (cbP) {errno = EINPROGRESS; return -1;} if (fp->Finalize(&Status)) return fp->FDNum(); - return XrdPosixMap::Result(Status); + return XrdPosixMap::Result(Status,XrdPosixGlobals::ecMsg,true); } /******************************************************************************/ @@ -701,14 +695,16 @@ DIR* XrdPosixXrootd::Opendir(const char *path) // Get a new directory object // if (!(dP = new XrdPosixDir(path))) - {errno = ENOMEM; return (DIR *)0;} + {XrdPosixGlobals::ecMsg.SetErrno(ENOMEM); + return (DIR*)0; + } // Assign a file descriptor to this file // if (!(dP->AssignFD())) {delete dP; - errno = EMFILE; - return (DIR *)0; + XrdPosixGlobals::ecMsg.SetErrno(EMFILE); + return (DIR*)0; } // Open the directory @@ -719,7 +715,7 @@ DIR* XrdPosixXrootd::Opendir(const char *path) // rc = errno; delete dP; - errno = rc; + errno = rc; // Restore saved errno return (DIR *)0; } @@ -735,18 +731,20 @@ ssize_t XrdPosixXrootd::Pread(int fildes, void *buf, size_t nbyte, off_t offset) // Find the file object // - if (!(fp = XrdPosixObject::File(fildes))) return -1; + if (!(fp = XrdPosixObject::File(fildes))) + return XrdPosixGlobals::ecMsg.SetErrno(EBADF); // Make sure the size is not too large // - if (nbyte > (size_t)0x7fffffff) return Fault(fp,EOVERFLOW); + if (nbyte > (size_t)0x7fffffff) + return Fault(fp, EOVERFLOW, "read size too large"); else iosz = static_cast(nbyte); // Issue the read // offs = static_cast(offset); bytes = fp->XCio->Read((char *)buf, offs, (int)iosz); - if (bytes < 0) return Fault(fp,-bytes); + if (bytes < 0) return Fault(fp,-bytes,"*"); // All went well // @@ -771,7 +769,7 @@ void XrdPosixXrootd::Pread(int fildes, void *buf, size_t nbyte, off_t offset, // if (nbyte > (size_t)0x7fffffff) {fp->UnLock(); - errno = EOVERFLOW; + fp->ecMsg.SetErrno(EOVERFLOW,0,"read size too large"); cbp->Complete(-1); return; } @@ -804,14 +802,15 @@ ssize_t XrdPosixXrootd::Pwrite(int fildes, const void *buf, size_t nbyte, off_t // Make sure the size is not too large // - if (nbyte > (size_t)0x7fffffff) return Fault(fp,EOVERFLOW); + if (nbyte > (size_t)0x7fffffff) + return Fault(fp,EOVERFLOW,"write size too large"); else iosz = static_cast(nbyte); // Issue the write // offs = static_cast(offset); bytes = fp->XCio->Write((char *)buf, offs, (int)iosz); - if (bytes < 0) return Fault(fp,-bytes); + if (bytes < 0) return Fault(fp,-bytes,"*"); // All went well // @@ -837,7 +836,7 @@ void XrdPosixXrootd::Pwrite(int fildes, const void *buf, size_t nbyte, // if (nbyte > (size_t)0x7fffffff) {fp->UnLock(); - errno = EOVERFLOW; + fp->ecMsg.SetErrno(EOVERFLOW,0,"read size too large"); cbp->Complete(-1); return; } @@ -982,7 +981,9 @@ struct dirent64* XrdPosixXrootd::Readdir64(DIR *dirp) // Find the object // if (!(dP = XrdPosixObject::Dir(fildes))) - {errno = EBADF; return 0;} + {XrdPosixGlobals::ecMsg.SetErrno(EBADF); + return (dirent64*)0; + } // Get the next directory entry // @@ -1049,12 +1050,13 @@ int XrdPosixXrootd::Readdir64_r(DIR *dirp, struct dirent64 *entry, int XrdPosixXrootd::Rename(const char *oldpath, const char *newpath) { - XrdPosixAdmin admin(oldpath); + XrdPosixAdmin admin(oldpath,XrdPosixGlobals::ecMsg); XrdCl::URL newUrl((std::string)newpath); // Make sure the admin is OK and the new url is valid // - if (!admin.isOK() || !newUrl.IsValid()) {errno = EINVAL; return -1;} + if (!admin.isOK() || !newUrl.IsValid()) + return XrdPosixGlobals::ecMsg.SetErrno(EINVAL); // Issue rename to he cache (it really should just deep-six both files) // @@ -1068,10 +1070,11 @@ int XrdPosixXrootd::Rename(const char *oldpath, const char *newpath) // Issue the rename // if (XrdPosixGlobals::usingEC) - return EcRename(oldpath, newpath, &admin); + return EcRename(oldpath, newpath, admin); return XrdPosixMap::Result(admin.Xrd.Mv(admin.Url.GetPathWithParams(), - newUrl.GetPathWithParams())); + newUrl.GetPathWithParams()), + XrdPosixGlobals::ecMsg, true); } /******************************************************************************/ @@ -1097,7 +1100,7 @@ void XrdPosixXrootd::Rewinddir(DIR *dirp) int XrdPosixXrootd::Rmdir(const char *path) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); // Make sure the admin is OK // @@ -1113,7 +1116,8 @@ int XrdPosixXrootd::Rmdir(const char *path) // Issue the rmdir // - return XrdPosixMap::Result(admin.Xrd.RmDir(admin.Url.GetPathWithParams())); + return XrdPosixMap::Result(admin.Xrd.RmDir(admin.Url.GetPathWithParams()), + XrdPosixGlobals::ecMsg, true); } /******************************************************************************/ @@ -1145,7 +1149,7 @@ void XrdPosixXrootd::Seekdir(DIR *dirp, long loc) int XrdPosixXrootd::Stat(const char *path, struct stat *buf) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); // Make sure the admin is OK // @@ -1162,13 +1166,13 @@ int XrdPosixXrootd::Stat(const char *path, struct stat *buf) if (!statX.path) return -1; int rc = XrdPosixGlobals::theCache->Stat(statX.path, *buf); if (!rc) return 0; - if (rc < 0) {errno = -rc; return -1;} + if (rc < 0) {errno = -rc; return -1;} // does the cache set this??? } // Issue the stat and verify that all went well // if (XrdPosixGlobals::usingEC) - return EcStat(path, buf, &admin); + return EcStat(path, buf, admin); if (!admin.Stat(*buf)) return -1; return 0; @@ -1220,7 +1224,7 @@ int XrdPosixXrootd::Statvfs(const char *path, struct statvfs *buf) static const int szVFS = sizeof(buf->f_bfree); static const long long max32 = 0x7fffffffLL; - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); XrdCl::StatInfoVFS *vfsStat; long long rwFree, ssFree, rwBlks; @@ -1233,7 +1237,8 @@ int XrdPosixXrootd::Statvfs(const char *path, struct statvfs *buf) // Issue the statfvs call // if (XrdPosixMap::Result(admin.Xrd.StatVFS(admin.Url.GetPathWithParams(), - vfsStat)) < 0) return -1; + vfsStat), + XrdPosixGlobals::ecMsg) < 0) return -1; // Extract out the information // @@ -1289,7 +1294,7 @@ long XrdPosixXrootd::Telldir(DIR *dirp) // Find the object // if (!(dP = XrdPosixObject::Dir(fildes))) - {errno = EBADF; return 0;} + return XrdPosixGlobals::ecMsg.SetErrno(EBADF,0); // Tell the current directory location // @@ -1304,7 +1309,7 @@ long XrdPosixXrootd::Telldir(DIR *dirp) int XrdPosixXrootd::Truncate(const char *path, off_t Size) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); uint64_t tSize = static_cast(Size); // Make sure the admin is OK @@ -1322,7 +1327,8 @@ int XrdPosixXrootd::Truncate(const char *path, off_t Size) // Issue the truncate to the origin // std::string urlp = admin.Url.GetPathWithParams(); - return XrdPosixMap::Result(admin.Xrd.Truncate(urlp,tSize)); + return XrdPosixMap::Result(admin.Xrd.Truncate(urlp,tSize), + XrdPosixGlobals::ecMsg,true); } /******************************************************************************/ @@ -1331,7 +1337,7 @@ int XrdPosixXrootd::Truncate(const char *path, off_t Size) int XrdPosixXrootd::Unlink(const char *path) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); // Make sure the admin is OK // @@ -1348,9 +1354,10 @@ int XrdPosixXrootd::Unlink(const char *path) // Issue the UnLink // if (XrdPosixGlobals::usingEC) - return EcUnlink(path, &admin); + return EcUnlink(path, admin); - return XrdPosixMap::Result(admin.Xrd.Rm(admin.Url.GetPathWithParams())); + return XrdPosixMap::Result(admin.Xrd.Rm(admin.Url.GetPathWithParams()), + XrdPosixGlobals::ecMsg, true); } /******************************************************************************/ @@ -1439,7 +1446,7 @@ bool XrdPosixXrootd::myFD(int fd) int XrdPosixXrootd::QueryChksum(const char *path, time_t &Mtime, char *value, int vsize) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); // Stat the file first to allow vectoring of the request to the right server // @@ -1450,13 +1457,51 @@ int XrdPosixXrootd::QueryChksum(const char *path, time_t &Mtime, return admin.Query(XrdCl::QueryCode::Checksum, value, vsize); } +/******************************************************************************/ +/* Q u e r y E r r o r */ +/******************************************************************************/ + +int XrdPosixXrootd::QueryError(std::string& emsg, int fd, bool reset) +{ + XrdOucECMsg* ecmP; + +// If global wanted then use that one otherwise find the object specific one +// + if (fd < 0) ecmP = &XrdPosixGlobals::ecMsg; + else {XrdPosixFile *fp; + if (!(fp = XrdPosixObject::File(fd))) return -1; + ecmP = fp->getECMsg(); + } + +// Return the message information +// + return ecmP->Get(emsg, reset); +} + +/******************************************************************************/ + +int XrdPosixXrootd::QueryError(std::string& emsg, DIR* dirP, bool reset) +{ + XrdPosixDir *dP; + int fildes = XrdPosixDir::dirNo(dirP); + +// Find the object +// + if (!(dP = XrdPosixObject::Dir(fildes))) + return XrdPosixGlobals::ecMsg.SetErrno(EBADF); + +// Return result +// + return dP->getECMsg()->Get(emsg, reset); +} + /******************************************************************************/ /* Q u e r y O p a q u e */ /******************************************************************************/ long long XrdPosixXrootd::QueryOpaque(const char *path, char *value, int size) { - XrdPosixAdmin admin(path); + XrdPosixAdmin admin(path,XrdPosixGlobals::ecMsg); // Stat the file first to allow vectoring of the request to the right server // @@ -1474,17 +1519,18 @@ long long XrdPosixXrootd::QueryOpaque(const char *path, char *value, int size) /* F a u l t */ /******************************************************************************/ -int XrdPosixXrootd::Fault(XrdPosixFile *fp, int ecode) +int XrdPosixXrootd::Fault(XrdPosixFile *fp, int ecode, const char *msg) { fp->UnLock(); - errno = ecode; - return -1; + return XrdPosixGlobals::ecMsg.SetErrno(ecode, -1, msg); } /******************************************************************************/ /* E c R e n a m e */ /******************************************************************************/ -int XrdPosixXrootd::EcRename(const char *oldpath, const char *newpath, XrdPosixAdmin *admin) + +int XrdPosixXrootd::EcRename(const char *oldpath, const char *newpath, + XrdPosixAdmin &admin) { XrdCl::URL url(oldpath); XrdCl::URL newUrl(newpath); @@ -1503,8 +1549,9 @@ int XrdPosixXrootd::EcRename(const char *oldpath, const char *newpath, XrdPosixA || queryResp->ToString() == "server\n") { if (queryResp) delete queryResp; - return XrdPosixMap::Result(admin->Xrd.Mv(admin->Url.GetPathWithParams(), - newUrl.GetPathWithParams())); + return XrdPosixMap::Result(admin.Xrd.Mv(admin.Url.GetPathWithParams(), + newUrl.GetPathWithParams()), + XrdPosixGlobals::ecMsg, true); } else if (queryResp) delete queryResp; @@ -1512,32 +1559,27 @@ int XrdPosixXrootd::EcRename(const char *oldpath, const char *newpath, XrdPosixA st = fs.DeepLocate("*", XrdCl::OpenFlags::None, info ); std::unique_ptr ptr( info ); if( !st.IsOK() ) - { - errno = ENOENT; - return -1; - } + return XrdPosixMap::Result(st, XrdPosixGlobals::ecMsg, true); // check if this is a file or a dir, do not support dir renaming in EC struct stat buf; - if (XrdPosixXrootd::Stat(oldpath, &buf) != 0 || ! S_ISREG(buf.st_mode)) - { - errno = ENOTSUP; - return -1; - } - if (XrdPosixXrootd::Stat(newpath, &buf) == 0) - { - errno = ENOTSUP; - return -1; - } + if (XrdPosixXrootd::Stat(oldpath, &buf) != 0) + return XrdPosixGlobals::ecMsg.SetErrno(ENOENT); + if ( ! S_ISREG(buf.st_mode)) + return XrdPosixGlobals::ecMsg.SetErrno(ENOTSUP); + + if (XrdPosixXrootd::Stat(newpath, &buf) == 0) + return XrdPosixGlobals::ecMsg.SetErrno(EEXIST); int rc = -ENOENT; for( size_t i = 0; i < info->GetSize(); ++i ) { std::string url_i = "root://" + info->At(i).GetAddress() + "/" + file; - XrdPosixAdmin *admin_i = new XrdPosixAdmin(url_i.c_str()); + XrdPosixAdmin *admin_i = new XrdPosixAdmin(url_i.c_str(),admin.ecMsg); int x = XrdPosixMap::Result(admin_i->Xrd.Mv(admin_i->Url.GetPathWithParams(), - newUrl.GetPathWithParams())); + newUrl.GetPathWithParams()), + admin.ecMsg); if (x != -ENOENT && rc != 0) rc = x; if (admin_i) delete admin_i; @@ -1548,7 +1590,9 @@ int XrdPosixXrootd::EcRename(const char *oldpath, const char *newpath, XrdPosixA /******************************************************************************/ /* E c S t a t */ /******************************************************************************/ -int XrdPosixXrootd::EcStat(const char *path, struct stat *buf, XrdPosixAdmin *admin) + +int XrdPosixXrootd::EcStat(const char *path, struct stat *buf, + XrdPosixAdmin &admin) { XrdCl::URL url(path); std::string file = url.GetPath(); @@ -1569,7 +1613,7 @@ int XrdPosixXrootd::EcStat(const char *path, struct stat *buf, XrdPosixAdmin *ad || queryResp->ToString() == "server\n") { if (queryResp) delete queryResp; - if (!admin->Stat(*buf)) + if (!admin.Stat(*buf)) return -1; else { @@ -1603,7 +1647,7 @@ int XrdPosixXrootd::EcStat(const char *path, struct stat *buf, XrdPosixAdmin *ad for( size_t i = 0; i < info->GetSize(); ++i ) { std::string url_i = "root://" + info->At(i).GetAddress() + "/" + file; - XrdPosixAdmin *admin_i = new XrdPosixAdmin(url_i.c_str()); + XrdPosixAdmin *admin_i = new XrdPosixAdmin(url_i.c_str(),admin.ecMsg); if (admin_i->Stat(buf_i)) { @@ -1652,7 +1696,8 @@ int XrdPosixXrootd::EcStat(const char *path, struct stat *buf, XrdPosixAdmin *ad /******************************************************************************/ /* E c U n l i n k */ /******************************************************************************/ -int XrdPosixXrootd::EcUnlink(const char *path, XrdPosixAdmin *admin) + +int XrdPosixXrootd::EcUnlink(const char *path, XrdPosixAdmin &admin) { XrdCl::URL url(path); std::string file = url.GetPath(); @@ -1668,7 +1713,8 @@ int XrdPosixXrootd::EcUnlink(const char *path, XrdPosixAdmin *admin) || queryResp->ToString() == "server\n") { if (queryResp) delete queryResp; - return XrdPosixMap::Result(admin->Xrd.Rm(admin->Url.GetPathWithParams())); + return XrdPosixMap::Result(admin.Xrd.Rm(admin.Url.GetPathWithParams()), + admin.ecMsg, true); } else if (queryResp) delete queryResp; @@ -1676,16 +1722,16 @@ int XrdPosixXrootd::EcUnlink(const char *path, XrdPosixAdmin *admin) st = fs.DeepLocate("*", XrdCl::OpenFlags::None, info ); std::unique_ptr ptr( info ); if( !st.IsOK() ) - { - errno = ENOENT; - return -1; - } + return XrdPosixMap::Result(st, admin.ecMsg, true); + int rc = -ENOENT; for( size_t i = 0; i < info->GetSize(); ++i ) { std::string url_i = "root://" + info->At(i).GetAddress() + "/" + file; - XrdPosixAdmin *admin_i = new XrdPosixAdmin(url_i.c_str()); - int x = XrdPosixMap::Result(admin_i->Xrd.Rm(admin_i->Url.GetPathWithParams())); + XrdPosixAdmin *admin_i = new XrdPosixAdmin(url_i.c_str(),admin.ecMsg); + int x = XrdPosixMap::Result(admin_i-> + Xrd.Rm(admin_i->Url.GetPathWithParams()), + admin.ecMsg); if (x != -ENOENT && rc != 0) rc = x; if (admin_i) delete admin_i; diff --git a/src/XrdPosix/XrdPosixXrootd.hh b/src/XrdPosix/XrdPosixXrootd.hh index 2902af5199e..bcf9e0123fe 100644 --- a/src/XrdPosix/XrdPosixXrootd.hh +++ b/src/XrdPosix/XrdPosixXrootd.hh @@ -31,6 +31,7 @@ /* Modified by Frank Winklmeier to add the full Posix file system definition. */ /******************************************************************************/ +#include #include #include #include @@ -207,6 +208,27 @@ static void Pwrite(int fildes, const void *buf, size_t nbyte, off_t offset, static int QueryChksum(const char *path, time_t &mtime, char *buff, int blen); +//----------------------------------------------------------------------------- +//! QueryError() is a POSIX extension and returns extended information about +//! the last error returned from a call to a POSIX function. +//! +//! @param emsg Reference to a string to hold the retruned message text. +//! @param fd The file descriptor associated with the error. A negative +//! value returns the last error encountered on the calling +//! thread for the last function not releated to a file descritor. +//! dirP Get the error associated with the last directory operation. +//! @param reset When true (the default) clears the error information. +//! +//! @return The error code and the associated message via parameter emsg. +//! A zero return indicates that no error information is available. +//! A -1 return indicates the call was bad itself because either the +//! fd or dirP was invalid. +//----------------------------------------------------------------------------- + +static int QueryError(std::string& emsg, int fd=-1, bool reset=true); + +static int QueryError(std::string& emsg, DIR* dirP, bool reset=true); + //----------------------------------------------------------------------------- //! QueryOpaque() is a POSIX extension and returns a file's implementation //! specific information. @@ -368,16 +390,16 @@ static bool myFD(int fd); private: -static int Fault(XrdPosixFile *fp, int ecode); +static int Fault(XrdPosixFile *fp, int ecode, const char *msg=0); static int Open(const char *path, int oflag, mode_t mode, XrdPosixCallBack *cbP, XrdPosixInfo *infoP); static bool OpenCache(XrdPosixFile &file, XrdPosixInfo &Info); // functions that will be used when XrdEC is invoked -static int EcRename(const char*, const char*, XrdPosixAdmin*); -static int EcStat(const char*, struct stat*, XrdPosixAdmin*); -static int EcUnlink(const char*, XrdPosixAdmin*); +static int EcRename(const char*, const char*, XrdPosixAdmin&); +static int EcStat(const char*, struct stat*, XrdPosixAdmin&); +static int EcUnlink(const char*, XrdPosixAdmin&); static int baseFD; static int initDone; diff --git a/src/XrdPss/XrdPss.cc b/src/XrdPss/XrdPss.cc index 0891d5ba53a..ee61badaac4 100644 --- a/src/XrdPss/XrdPss.cc +++ b/src/XrdPss/XrdPss.cc @@ -95,8 +95,10 @@ class XrdScheduler; namespace XrdProxy { +thread_local XrdOucECMsg ecMsg("[pss]"); + static XrdPssSys XrdProxySS; - + XrdSysError eDest(0, "pss_"); XrdScheduler *schedP = 0; @@ -365,7 +367,7 @@ int XrdPssSys::Mkdir(const char *path, mode_t mode, int mkpath, XrdOucEnv *eP) // Simply return the proxied result here // - return (XrdPosixXrootd::Mkdir(pbuff, mode) ? -errno : XrdOssOK); + return (XrdPosixXrootd::Mkdir(pbuff, mode) ? Info(errno) : XrdOssOK); } /******************************************************************************/ @@ -1266,6 +1268,16 @@ int XrdPssFile::Ftruncate(unsigned long long flen) return (XrdPosixXrootd::Ftruncate(fd, flen) ? -errno : XrdOssOK); } +/******************************************************************************/ +/* I n t e r n a l M e t h o d s */ +/******************************************************************************/ + +int XrdPssSys::Info(int rc) +{ + XrdPosixXrootd::QueryError(XrdProxy::ecMsg.Msg()); + return -rc; +} + /******************************************************************************/ /* P 2 D S T */ /******************************************************************************/ diff --git a/src/XrdPss/XrdPss.hh b/src/XrdPss/XrdPss.hh index 631013ea6f5..96179574ded 100644 --- a/src/XrdPss/XrdPss.hh +++ b/src/XrdPss/XrdPss.hh @@ -35,6 +35,7 @@ #include #include #include "XrdSys/XrdSysHeaders.hh" +#include "XrdOuc/XrdOucECMsg.hh" #include "XrdOuc/XrdOucExport.hh" #include "XrdOuc/XrdOucName2Name.hh" #include "XrdOuc/XrdOucPList.hh" @@ -176,6 +177,7 @@ int Unlink(const char *, int Opts=0, XrdOucEnv *eP=0) override; static const int PolNum = 2; enum PolAct {PolPath = 0, PolObj = 1}; +static int Info(int rc); static int P2DST(int &retc, char *hBuff, int hBlen, PolAct pType, const char *path); static int P2OUT(char *pbuff, int pblen, XrdPssUrlInfo &uInfo); diff --git a/src/XrdUtils.cmake b/src/XrdUtils.cmake index 8621c3b974f..42544269cec 100644 --- a/src/XrdUtils.cmake +++ b/src/XrdUtils.cmake @@ -105,6 +105,7 @@ set ( XrdOucSources XrdOuc/XrdOucChkPnt.hh XrdOuc/XrdOucCRC.cc XrdOuc/XrdOucCRC.hh XrdOuc/XrdOucCRC32C.cc XrdOuc/XrdOucCRC32C.hh + XrdOuc/XrdOucECMsg.cc XrdOuc/XrdOucECMsg.hh XrdOuc/XrdOucEnv.cc XrdOuc/XrdOucEnv.hh XrdOuc/XrdOucERoute.cc XrdOuc/XrdOucERoute.hh XrdOuc/XrdOucErrInfo.hh From 8f76a42750ef155d0b5f1bd6f3d056ea474c87e5 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 13 Nov 2023 08:51:54 -0800 Subject: [PATCH 660/773] [POSIX] Avoid clang variadic argument complaint --- src/XrdPosix/XrdPosixAdmin.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdPosix/XrdPosixAdmin.hh b/src/XrdPosix/XrdPosixAdmin.hh index f56ce54c1bf..9e0d86acee7 100644 --- a/src/XrdPosix/XrdPosixAdmin.hh +++ b/src/XrdPosix/XrdPosixAdmin.hh @@ -55,7 +55,7 @@ XrdOucECMsg& ecMsg; bool isOK() {if (Url.IsValid()) return true; ecMsg.Set(EINVAL, 0); ecMsg.Msgf("PosixAdmin", "url '%s' is invalid", - Url.GetURL()); + Url.GetURL().c_str()); errno = EINVAL; return false; } From 1e2c84c113e17f6819ac48434517e7de5698faa7 Mon Sep 17 00:00:00 2001 From: Diego Davila Date: Tue, 9 Jan 2024 11:31:12 -0800 Subject: [PATCH 661/773] Add option tpc.route to force the Destination IP address on a HTTP-TPC --- src/XrdTpc/XrdTpcConfigure.cc | 17 ++++++++++++++++- src/XrdTpc/XrdTpcTPC.cc | 34 ++++++++++++++++++++++++++++++++++ src/XrdTpc/XrdTpcTPC.hh | 2 ++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/XrdTpc/XrdTpcConfigure.cc b/src/XrdTpc/XrdTpcConfigure.cc index 72c7072b543..55747b1b33f 100644 --- a/src/XrdTpc/XrdTpcConfigure.cc +++ b/src/XrdTpc/XrdTpcConfigure.cc @@ -56,7 +56,22 @@ bool TPCHandler::Configure(const char *configfn, XrdOucEnv *myEnv) Config.Close(); return false; } - } else if (!strcmp("tpc.timeout", val)) { + } else if (!strcmp("tpc.fixed_route", val)) { + if (!(val = Config.GetWord())) { + Config.Close(); + m_log.Emsg("Config", "tpc.fixed_route value not specified"); + return false; + } + if (!strcmp("1", val) || !strcasecmp("yes", val) || !strcasecmp("true", val)) { + m_fixed_route= true; + } else if (!strcmp("0", val) || !strcasecmp("no", val) || !strcasecmp("false", val)) { + m_fixed_route= false; + } else{ + Config.Close(); + m_log.Emsg("Config", "tpc.fixed_route value is invalid", val); + return false; + } + } else if (!strcmp("tpc.timeout", val)) { if (!(val = Config.GetWord())) { m_log.Emsg("Config","tpc.timeout value not specified."); return false; } diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index bd35d8ce79e..49f7dd4477f 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -1,5 +1,6 @@ #include "XrdHttp/XrdHttpExtHandler.hh" #include "XrdNet/XrdNetAddr.hh" +#include "XrdNet/XrdNetUtils.hh" #include "XrdOuc/XrdOucEnv.hh" #include "XrdSec/XrdSecEntity.hh" #include "XrdSfs/XrdSfsInterface.hh" @@ -321,6 +322,7 @@ TPCHandler::~TPCHandler() { TPCHandler::TPCHandler(XrdSysError *log, const char *config, XrdOucEnv *myEnv) : m_desthttps(false), + m_fixed_route(false), m_timeout(60), m_first_timeout(120), m_log(log->logger(), "TPC_"), @@ -952,6 +954,38 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) logTransferEvent(LogMask::Error, rec, "PULL_FAIL", msg); return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); } + // ddavila 2023-01-05: + // The following change was required by the Rucio/SENSE project where + // multiple IP addresses, each from a different subnet, are assigned to a + // single server and routed differently by SENSE. + // The above requires the server to utilize the same IP, that was used to + // start the TPC, for the resolution of the given TPC instead of + // using any of the IPs available. + if (m_fixed_route){ + XrdNetAddr *nP; + int numIP = 0; + char buff[1024]; + char * ip; + + // Get the hostname used to contact the server from the http header + auto host_header = req.headers.find("Host"); + std::string host_used; + if (host_header != req.headers.end()) { + host_used = host_header->second; + } + + // Get the IP addresses associated with the above hostname + XrdNetUtils::GetAddrs(host_used.c_str(), &nP, numIP, XrdNetUtils::prefAuto, 0); + int ip_size = nP[0].Format(buff, 1024, XrdNetAddrInfo::fmtAddr,XrdNetAddrInfo::noPort); + ip = (char *)malloc(ip_size-1); + + // Substring to get only the address, remove brackets and garbage + memcpy(ip, buff+1, ip_size-2); + ip[ip_size-2]='\0'; + logTransferEvent(LogMask::Info, rec, "LOCAL IP", ip); + + curl_easy_setopt(curl, CURLOPT_INTERFACE, ip); + } curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // curl_easy_setopt(curl,CURLOPT_SOCKOPTFUNCTION,sockopt_setcloexec_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback); diff --git a/src/XrdTpc/XrdTpcTPC.hh b/src/XrdTpc/XrdTpcTPC.hh index 76f600c58c9..d62ce9379fc 100644 --- a/src/XrdTpc/XrdTpcTPC.hh +++ b/src/XrdTpc/XrdTpcTPC.hh @@ -144,6 +144,8 @@ private: static size_t m_block_size; static size_t m_small_block_size; bool m_desthttps; + bool m_fixed_route; // If 'true' the Destination IP in an HTTP-TPC is forced to be the same as the IP used to contact the server + // when 'false' any IP available can be selected int m_timeout; // the 'timeout interval'; if no bytes have been received during this time period, abort the transfer. int m_first_timeout; // the 'first timeout interval'; the amount of time we're willing to wait to get the first byte. // Unless explicitly specified, this is 2x the timeout interval. From d3165b3c73152ac2e37d972b6928125c27f3218e Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Thu, 25 Jan 2024 09:46:39 -0600 Subject: [PATCH 662/773] Add support for pelican:// protocol In https://github.com/PelicanPlatform/xrdcl-pelican, we are developing a XrdCl plugin that can talk to the infrastructure for a new project, christening the URL scheme `pelican://`. This commit adds the new schema so it can be utilized from both xrdcp (primarily for testing) and XCache. --- src/XrdApps/XrdCpConfig.cc | 2 ++ src/XrdApps/XrdCpFile.cc | 1 + src/XrdApps/XrdCpFile.hh | 2 +- src/XrdPss/XrdPssUtils.cc | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/XrdApps/XrdCpConfig.cc b/src/XrdApps/XrdCpConfig.cc index 890f80198cc..7f8e8702efa 100644 --- a/src/XrdApps/XrdCpConfig.cc +++ b/src/XrdApps/XrdCpConfig.cc @@ -385,6 +385,7 @@ do{while(optind < Argc && Legacy(optind)) {} if (dstFile->Protocol != XrdCpFile::isFile && dstFile->Protocol != XrdCpFile::isStdIO && dstFile->Protocol != XrdCpFile::isXroot + && dstFile->Protocol != XrdCpFile::isPelican && (!Want(DoAllowHttp) && ((dstFile->Protocol == XrdCpFile::isHttp) || (dstFile->Protocol == XrdCpFile::isHttps)))) {FMSG(dstFile->ProtName <<"file protocol is not supported.", 22)} @@ -903,6 +904,7 @@ void XrdCpConfig::ProcFile(const char *fname) } else if (!((pFile->Protocol == XrdCpFile::isXroot) || (pFile->Protocol == XrdCpFile::isXroots) || + (pFile->Protocol == XrdCpFile::isPelican) || (Want(DoAllowHttp) && ((pFile->Protocol == XrdCpFile::isHttp) || (pFile->Protocol == XrdCpFile::isHttps))))) {FMSG(pFile->ProtName <<" file protocol is not supported.", 22)} diff --git a/src/XrdApps/XrdCpFile.cc b/src/XrdApps/XrdCpFile.cc index a6f8a6496e2..e1e5dc98086 100644 --- a/src/XrdApps/XrdCpFile.cc +++ b/src/XrdApps/XrdCpFile.cc @@ -56,6 +56,7 @@ XrdCpFile::XrdCpFile(const char *FSpec, int &badURL) {"root://", 7, isXroot}, {"roots://", 8, isXroots}, {"http://", 7, isHttp}, + {"pelican://", 10, isPelican}, {"https://", 8, isHttps} }; static int pTnum = sizeof(pTab)/sizeof(struct proto); diff --git a/src/XrdApps/XrdCpFile.hh b/src/XrdApps/XrdCpFile.hh index ef09301b56c..03972c360d8 100644 --- a/src/XrdApps/XrdCpFile.hh +++ b/src/XrdApps/XrdCpFile.hh @@ -38,7 +38,7 @@ class XrdCpFile public: enum PType {isOther = 0, isDir, isFile, isStdIO, - isXroot, isXroots, isHttp, isHttps, isDevNull, isDevZero + isXroot, isXroots, isHttp, isHttps, isPelican, isDevNull, isDevZero }; XrdCpFile *Next; // -> Next file in list diff --git a/src/XrdPss/XrdPssUtils.cc b/src/XrdPss/XrdPssUtils.cc index be14fa55c9a..42f37534f14 100644 --- a/src/XrdPss/XrdPssUtils.cc +++ b/src/XrdPss/XrdPssUtils.cc @@ -42,7 +42,8 @@ namespace struct pEnt {const char *pname; int pnlen;} pTab[] = {{ "https://", 8}, { "http://", 7}, { "roots://", 8}, { "root://", 7}, - {"xroots://", 9}, {"xroot://", 8} + {"xroots://", 9}, {"xroot://", 8}, + {"pelican://", 10} }; int pTNum = sizeof(pTab)/sizeof(pEnt); int xrBeg = 2; From df1471f5a1005b3b33d677c9d04bb4e22760397b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 14:25:41 +0100 Subject: [PATCH 663/773] [XrdCeph] Migrate tests to GoogleTest and run with ctest --- src/XrdCeph/src/XrdCeph.cmake | 8 +- src/XrdCeph/tests/CMakeLists.txt | 11 +- src/XrdCeph/tests/XrdCeph.cc | 111 +++++++++++++ src/XrdCeph/tests/XrdCephTests/CMakeLists.txt | 27 ---- .../tests/XrdCephTests/CephParsingTest.cc | 149 ------------------ tests/CMakeLists.txt | 4 - tests/XrdCephTests/CMakeLists.txt | 21 --- tests/XrdCephTests/CephParsingTest.cc | 149 ------------------ 8 files changed, 124 insertions(+), 356 deletions(-) create mode 100644 src/XrdCeph/tests/XrdCeph.cc delete mode 100644 src/XrdCeph/tests/XrdCephTests/CMakeLists.txt delete mode 100644 src/XrdCeph/tests/XrdCephTests/CephParsingTest.cc delete mode 100644 tests/XrdCephTests/CMakeLists.txt delete mode 100644 tests/XrdCephTests/CephParsingTest.cc diff --git a/src/XrdCeph/src/XrdCeph.cmake b/src/XrdCeph/src/XrdCeph.cmake index 60fb1da2099..5277df3fc5b 100644 --- a/src/XrdCeph/src/XrdCeph.cmake +++ b/src/XrdCeph/src/XrdCeph.cmake @@ -1,8 +1,3 @@ -include_directories( ${XROOTD_INCLUDE_DIR} ) -include_directories( ${RADOS_INCLUDE_DIR} ) -include_directories( ${CMAKE_SOURCE_DIR}/src ) - - #------------------------------------------------------------------------------- # XrdCephPosix library version #------------------------------------------------------------------------------- @@ -28,6 +23,9 @@ target_link_libraries( ${XROOTD_LIBRARIES} ${RADOS_LIBS} ) +target_include_directories( + XrdCephPosix PUBLIC ${RADOS_INCLUDE_DIR} $) + set_target_properties( XrdCephPosix PROPERTIES diff --git a/src/XrdCeph/tests/CMakeLists.txt b/src/XrdCeph/tests/CMakeLists.txt index 7216d36fd8e..3b38c7dcd4e 100644 --- a/src/XrdCeph/tests/CMakeLists.txt +++ b/src/XrdCeph/tests/CMakeLists.txt @@ -1 +1,10 @@ -add_subdirectory( XrdCephTests ) +find_package(GTest REQUIRED) + +include(GoogleTest) + +add_executable(xrdceph-unit-tests XrdCeph.cc) + +target_link_libraries(xrdceph-unit-tests + XrdCephPosix GTest::GTest GTest::Main) + +gtest_discover_tests(xrdceph-unit-tests TEST_PREFIX XrdCeph::) diff --git a/src/XrdCeph/tests/XrdCeph.cc b/src/XrdCeph/tests/XrdCeph.cc new file mode 100644 index 00000000000..cfed7cc9cce --- /dev/null +++ b/src/XrdCeph/tests/XrdCeph.cc @@ -0,0 +1,111 @@ +#undef NDEBUG + +#include +#include + +#include + +#define MB 1024 * 1024 + +struct CephFile { + std::string name; + std::string pool; + std::string userId; + unsigned int nbStripes; + unsigned long long stripeUnit; + unsigned long long objectSize; +}; + +void fillCephFile(const char *path, XrdOucEnv *env, CephFile &file); +void fillCephFileParams(const std::string ¶ms, XrdOucEnv *env, + CephFile &file); + +using namespace testing; + +class ParsingTest : public ::testing::Test {}; + +static CephFile parseParam(std::string param, XrdOucEnv *env = NULL) +{ + CephFile cf; + fillCephFileParams(param, env, cf); + return cf; +} + +static CephFile parseFile(std::string param, XrdOucEnv *env = NULL) +{ + CephFile cf; + fillCephFile(param.c_str(), env, cf); + return cf; +} + +static void checkResult(CephFile a, CephFile b) +{ + EXPECT_STREQ(a.name.c_str(), b.name.c_str()); + EXPECT_STREQ(a.pool.c_str(), b.pool.c_str()); + EXPECT_STREQ(a.userId.c_str(), b.userId.c_str()); + EXPECT_EQ(a.nbStripes, b.nbStripes); + EXPECT_EQ(a.stripeUnit, b.stripeUnit); + EXPECT_EQ(a.objectSize, b.objectSize); +} + +TEST(ParsingTest, Parameters) +{ + std::map inputs; + + inputs[""] = (CephFile){"", "default", "admin", 1, 4 * MB, 4 * MB}; + inputs["pool"] = (CephFile){"", "pool", "admin", 1, 4 * MB, 4 * MB}; + inputs["@"] = (CephFile){"", "default", "", 1, 4 * MB, 4 * MB}; + inputs["@pool"] = (CephFile){"", "pool", "", 1, 4 * MB, 4 * MB}; + inputs["user@"] = (CephFile){"", "default", "user", 1, 4 * MB, 4 * MB}; + inputs["user@pool"] = (CephFile){"", "pool", "user", 1, 4 * MB, 4 * MB}; + inputs["pool,1"] = (CephFile){"", "pool", "admin", 1, 4 * MB, 4 * MB}; + inputs["user@pool,1"] = (CephFile){"", "pool", "user", 1, 4 * MB, 4 * MB}; + inputs["pool,5"] = (CephFile){"", "pool", "admin", 5, 4 * MB, 4 * MB}; + inputs["user@pool,5"] = (CephFile){"", "pool", "user", 5, 4 * MB, 4 * MB}; + inputs["pool,5,200"] = (CephFile){"", "pool", "admin", 5, 200, 4 * MB}; + inputs["user@pool,5,200"] = (CephFile){"", "pool", "user", 5, 200, 4 * MB}; + inputs["pool,5,200,800"] = (CephFile){"", "pool", "admin", 5, 200, 800}; + inputs["user@pool,5,200,800"] = (CephFile){"", "pool", "user", 5, 200, 800}; + + for (auto it = inputs.begin(); it != inputs.end(); it++) + checkResult(parseParam(it->first), it->second); +} + +TEST(ParsingTest, File) +{ + std::vector filenames; + std::map inputs; + + filenames.push_back(""); + filenames.push_back("foo"); + filenames.push_back("/foo/bar"); + filenames.push_back("foo@bar"); + filenames.push_back("foo@bar,1"); + filenames.push_back("foo@bar,1,2"); + filenames.push_back("foo@bar,1,2,3"); + filenames.push_back("foo:bar"); + filenames.push_back(":foo"); + + for (auto it = filenames.begin(); it != filenames.end(); it++) { + if (std::string::npos == it->find(':')) + inputs[*it] = (CephFile){*it, "default", "admin", 1, 4 * MB, 4 * MB}; + + inputs[":" + *it] = (CephFile){*it, "default", "admin", 1, 4 * MB, 4 * MB}; + inputs["pool:" + *it] = (CephFile){*it, "pool", "admin", 1, 4 * MB, 4 * MB}; + inputs["@:" + *it] = (CephFile){*it, "default", "", 1, 4 * MB, 4 * MB}; + inputs["@pool:" + *it] = (CephFile){*it, "pool", "", 1, 4 * MB, 4 * MB}; + inputs["user@:" + *it] = (CephFile){*it, "default", "user", 1, 4 * MB, 4 * MB}; + inputs["user@pool:" + *it] = (CephFile){*it, "pool", "user", 1, 4 * MB, 4 * MB}; + inputs["pool,1:" + *it] = (CephFile){*it, "pool", "admin", 1, 4 * MB, 4 * MB}; + inputs["user@pool,1:" + *it] = (CephFile){*it, "pool", "user", 1, 4 * MB, 4 * MB}; + inputs["pool,5:" + *it] = (CephFile){*it, "pool", "admin", 5, 4 * MB, 4 * MB}; + inputs["user@pool,5:" + *it] = (CephFile){*it, "pool", "user", 5, 4 * MB, 4 * MB}; + inputs["pool,5,200:" + *it] = (CephFile){*it, "pool", "admin", 5, 200, 4 * MB}; + inputs["user@pool,5,200:" + *it] = (CephFile){*it, "pool", "user", 5, 200, 4 * MB}; + inputs["pool,5,200,800:" + *it] = (CephFile){*it, "pool", "admin", 5, 200, 800}; + inputs["user@pool,5,200,800:" + *it] = (CephFile){*it, "pool", "user", 5, 200, 800}; + } + + for (auto it = inputs.begin(); it != inputs.end(); it++) + checkResult(parseFile(it->first), it->second); +} diff --git a/src/XrdCeph/tests/XrdCephTests/CMakeLists.txt b/src/XrdCeph/tests/XrdCephTests/CMakeLists.txt deleted file mode 100644 index 2011390d85b..00000000000 --- a/src/XrdCeph/tests/XrdCephTests/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -message( "XROOTD_INCLUDE_DIR : ${XROOTD_INCLUDE_DIR}" ) - -add_library( - XrdCephTests MODULE - CephParsingTest.cc -) - -target_link_libraries( - XrdCephTests - pthread - ${CPPUNIT_LIBRARIES} - ${ZLIB_LIBRARY} - XrdCephPosix ) - -target_include_directories(XrdCephTests PRIVATE - ${CPPUNIT_INCLUDE_DIRS} - ${RADOS_INCLUDE_DIR} - ${XROOTD_INCLUDE_DIR} - ${PROJECT_SOURCE_DIR}/src) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdCephTests - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/src/XrdCeph/tests/XrdCephTests/CephParsingTest.cc b/src/XrdCeph/tests/XrdCephTests/CephParsingTest.cc deleted file mode 100644 index 194bd5e40c8..00000000000 --- a/src/XrdCeph/tests/XrdCephTests/CephParsingTest.cc +++ /dev/null @@ -1,149 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Sebastien Ponce -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that 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 - -#define MB 1024*1024 -struct CephFile { - std::string name; - std::string pool; - std::string userId; - unsigned int nbStripes; - unsigned long long stripeUnit; - unsigned long long objectSize; -}; -void fillCephFileParams(const std::string ¶ms, XrdOucEnv *env, CephFile &file); -void fillCephFile(const char *path, XrdOucEnv *env, CephFile &file); - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class CephParsingTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( CephParsingTest ); - CPPUNIT_TEST( ParamTest ); - CPPUNIT_TEST( FileTest ); - CPPUNIT_TEST_SUITE_END(); - void ParamTest(); - void FileTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( CephParsingTest ); - -//------------------------------------------------------------------------------ -// Helper functions -//------------------------------------------------------------------------------ -static CephFile parseParam(std::string param, XrdOucEnv *env= NULL) { - CephFile cf; - fillCephFileParams(param, env, cf); - return cf; -} - -static CephFile parseFile(std::string param, XrdOucEnv *env= NULL) { - CephFile cf; - fillCephFile(param.c_str(), env, cf); - return cf; -} - -void checkResult(CephFile a, CephFile b) { - std::cout << a.name << " " << a.pool << " " << a.userId - << " " << a.nbStripes << " " << a.stripeUnit << " " << a.objectSize - << " / " << 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); -} - -//------------------------------------------------------------------------------ -// Param test -//------------------------------------------------------------------------------ -void CephParsingTest::ParamTest() { - std::map inputs; - inputs[""] = (CephFile){"", "default", "admin", 1, 4*MB, 4*MB}; - inputs["pool"] = (CephFile){"", "pool", "admin", 1, 4*MB, 4*MB}; - inputs["@"] = (CephFile){"", "default", "", 1, 4*MB, 4*MB}; - inputs["@pool"] = (CephFile){"", "pool", "", 1, 4*MB, 4*MB}; - inputs["user@"] = (CephFile){"", "default", "user", 1, 4*MB, 4*MB}; - inputs["user@pool"] = (CephFile){"", "pool", "user", 1, 4*MB, 4*MB}; - inputs["pool,1"] = (CephFile){"", "pool", "admin", 1, 4*MB, 4*MB}; - inputs["user@pool,1"] = (CephFile){"", "pool", "user", 1, 4*MB, 4*MB}; - inputs["pool,5"] = (CephFile){"", "pool", "admin", 5, 4*MB, 4*MB}; - inputs["user@pool,5"] = (CephFile){"", "pool", "user", 5, 4*MB, 4*MB}; - inputs["pool,5,200"] = (CephFile){"", "pool", "admin", 5, 200, 4*MB}; - inputs["user@pool,5,200"] = (CephFile){"", "pool", "user", 5, 200, 4*MB}; - inputs["pool,5,200,800"] = (CephFile){"", "pool", "admin", 5, 200, 800}; - inputs["user@pool,5,200,800"] = (CephFile){"", "pool", "user", 5, 200, 800}; - for (std::map::const_iterator it = inputs.begin(); - it != inputs.end(); - it++) { - std::cout << it->first << std::endl; - checkResult(parseParam(it->first), it->second); - } -} - -//------------------------------------------------------------------------------ -// File test -//------------------------------------------------------------------------------ -void CephParsingTest::FileTest() { - std::map inputs; - std::vector filenames; - filenames.push_back(""); - filenames.push_back("foo"); - filenames.push_back("/foo/bar"); - filenames.push_back("foo@bar"); - filenames.push_back("foo@bar,1"); - filenames.push_back("foo@bar,1,2"); - filenames.push_back("foo@bar,1,2,3"); - filenames.push_back("foo:bar"); - filenames.push_back(":foo"); - for (std::vector::const_iterator it = filenames.begin(); - it != filenames.end(); - it++) { - if (std::string::npos == it->find(':')) { - inputs[*it] = (CephFile){*it, "default", "admin", 1, 4*MB, 4*MB}; - } - inputs[":" + *it] = (CephFile){*it, "default", "admin", 1, 4*MB, 4*MB}; - inputs["pool:" + *it] = (CephFile){*it, "pool", "admin", 1, 4*MB, 4*MB}; - inputs["@:" + *it] = (CephFile){*it, "default", "", 1, 4*MB, 4*MB}; - inputs["@pool:" + *it] = (CephFile){*it, "pool", "", 1, 4*MB, 4*MB}; - inputs["user@:" + *it] = (CephFile){*it, "default", "user", 1, 4*MB, 4*MB}; - inputs["user@pool:" + *it] = (CephFile){*it, "pool", "user", 1, 4*MB, 4*MB}; - inputs["pool,1:" + *it] = (CephFile){*it, "pool", "admin", 1, 4*MB, 4*MB}; - inputs["user@pool,1:" + *it] = (CephFile){*it, "pool", "user", 1, 4*MB, 4*MB}; - inputs["pool,5:" + *it] = (CephFile){*it, "pool", "admin", 5, 4*MB, 4*MB}; - inputs["user@pool,5:" + *it] = (CephFile){*it, "pool", "user", 5, 4*MB, 4*MB}; - inputs["pool,5,200:" + *it] = (CephFile){*it, "pool", "admin", 5, 200, 4*MB}; - inputs["user@pool,5,200:" + *it] = (CephFile){*it, "pool", "user", 5, 200, 4*MB}; - inputs["pool,5,200,800:" + *it] = (CephFile){*it, "pool", "admin", 5, 200, 800}; - inputs["user@pool,5,200,800:" + *it] = (CephFile){*it, "pool", "user", 5, 200, 800}; - } - for (std::map::const_iterator it = inputs.begin(); - it != inputs.end(); - it++) { - std::cout << it->first << std::endl; - checkResult(parseFile(it->first), it->second); - } -} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 070d7bf7dfd..b982b387e24 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,9 +15,5 @@ if( BUILD_XRDEC ) add_subdirectory( XrdEc ) # new tests with GTest endif() -if( BUILD_CEPH ) - add_subdirectory( XrdCephTests ) -endif() - add_subdirectory( XRootD ) add_subdirectory( cluster ) diff --git a/tests/XrdCephTests/CMakeLists.txt b/tests/XrdCephTests/CMakeLists.txt deleted file mode 100644 index ed05bc465d0..00000000000 --- a/tests/XrdCephTests/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -add_library( - XrdCephTests MODULE - CephParsingTest.cc -) - -target_link_libraries( - XrdCephTests - ${CMAKE_THREAD_LIBS_INIT} - ${CPPUNIT_LIBRARIES} - ZLIB::ZLIB - XrdCephPosix ) - -target_include_directories( XrdCephTests PRIVATE ${CPPUNIT_INCLUDE_DIRS} ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdCephTests - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/tests/XrdCephTests/CephParsingTest.cc b/tests/XrdCephTests/CephParsingTest.cc deleted file mode 100644 index 06cb3366097..00000000000 --- a/tests/XrdCephTests/CephParsingTest.cc +++ /dev/null @@ -1,149 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Sebastien Ponce -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that 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 - -#define MB 1024*1024 -struct CephFile { - std::string name; - std::string pool; - std::string userId; - unsigned int nbStripes; - unsigned long long stripeUnit; - unsigned long long objectSize; -}; -void fillCephFileParams(const std::string ¶ms, XrdOucEnv *env, CephFile &file); -void fillCephFile(const char *path, XrdOucEnv *env, CephFile &file); - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class CephParsingTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( CephParsingTest ); - CPPUNIT_TEST( ParamTest ); - CPPUNIT_TEST( FileTest ); - CPPUNIT_TEST_SUITE_END(); - void ParamTest(); - void FileTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( CephParsingTest ); - -//------------------------------------------------------------------------------ -// Helper functions -//------------------------------------------------------------------------------ -static CephFile parseParam(std::string param, XrdOucEnv *env= NULL) { - CephFile cf; - fillCephFileParams(param, env, cf); - return cf; -} - -static CephFile parseFile(std::string param, XrdOucEnv *env= NULL) { - CephFile cf; - fillCephFile(param.c_str(), env, cf); - return cf; -} - -void checkResult(CephFile a, CephFile b) { - std::cout << a.name << " " << a.pool << " " << a.userId - << " " << a.nbStripes << " " << a.stripeUnit << " " << a.objectSize - << " / " << b.name << " " << b.pool << " " << b.userId - << " " << b.nbStripes << " " << b.stripeUnit << " " << b.objectSize - << std::endl; - 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); -} - -//------------------------------------------------------------------------------ -// Param test -//------------------------------------------------------------------------------ -void CephParsingTest::ParamTest() { - std::map inputs; - inputs[""] = (CephFile){"", "default", "admin", 1, 4*MB, 4*MB}; - inputs["pool"] = (CephFile){"", "pool", "admin", 1, 4*MB, 4*MB}; - inputs["@"] = (CephFile){"", "default", "", 1, 4*MB, 4*MB}; - inputs["@pool"] = (CephFile){"", "pool", "", 1, 4*MB, 4*MB}; - inputs["user@"] = (CephFile){"", "default", "user", 1, 4*MB, 4*MB}; - inputs["user@pool"] = (CephFile){"", "pool", "user", 1, 4*MB, 4*MB}; - inputs["pool,1"] = (CephFile){"", "pool", "admin", 1, 4*MB, 4*MB}; - inputs["user@pool,1"] = (CephFile){"", "pool", "user", 1, 4*MB, 4*MB}; - inputs["pool,5"] = (CephFile){"", "pool", "admin", 5, 4*MB, 4*MB}; - inputs["user@pool,5"] = (CephFile){"", "pool", "user", 5, 4*MB, 4*MB}; - inputs["pool,5,200"] = (CephFile){"", "pool", "admin", 5, 200, 4*MB}; - inputs["user@pool,5,200"] = (CephFile){"", "pool", "user", 5, 200, 4*MB}; - inputs["pool,5,200,800"] = (CephFile){"", "pool", "admin", 5, 200, 800}; - inputs["user@pool,5,200,800"] = (CephFile){"", "pool", "user", 5, 200, 800}; - for (std::map::const_iterator it = inputs.begin(); - it != inputs.end(); - it++) { - std::cout << it->first << std::endl; - checkResult(parseParam(it->first), it->second); - } -} - -//------------------------------------------------------------------------------ -// File test -//------------------------------------------------------------------------------ -void CephParsingTest::FileTest() { - std::map inputs; - std::vector filenames; - filenames.push_back(""); - filenames.push_back("foo"); - filenames.push_back("/foo/bar"); - filenames.push_back("foo@bar"); - filenames.push_back("foo@bar,1"); - filenames.push_back("foo@bar,1,2"); - filenames.push_back("foo@bar,1,2,3"); - filenames.push_back("foo:bar"); - filenames.push_back(":foo"); - for (std::vector::const_iterator it = filenames.begin(); - it != filenames.end(); - it++) { - if (std::string::npos == it->find(':')) { - inputs[*it] = (CephFile){*it, "default", "admin", 1, 4*MB, 4*MB}; - } - inputs[":" + *it] = (CephFile){*it, "default", "admin", 1, 4*MB, 4*MB}; - inputs["pool:" + *it] = (CephFile){*it, "pool", "admin", 1, 4*MB, 4*MB}; - inputs["@:" + *it] = (CephFile){*it, "default", "", 1, 4*MB, 4*MB}; - inputs["@pool:" + *it] = (CephFile){*it, "pool", "", 1, 4*MB, 4*MB}; - inputs["user@:" + *it] = (CephFile){*it, "default", "user", 1, 4*MB, 4*MB}; - inputs["user@pool:" + *it] = (CephFile){*it, "pool", "user", 1, 4*MB, 4*MB}; - inputs["pool,1:" + *it] = (CephFile){*it, "pool", "admin", 1, 4*MB, 4*MB}; - inputs["user@pool,1:" + *it] = (CephFile){*it, "pool", "user", 1, 4*MB, 4*MB}; - inputs["pool,5:" + *it] = (CephFile){*it, "pool", "admin", 5, 4*MB, 4*MB}; - inputs["user@pool,5:" + *it] = (CephFile){*it, "pool", "user", 5, 4*MB, 4*MB}; - inputs["pool,5,200:" + *it] = (CephFile){*it, "pool", "admin", 5, 200, 4*MB}; - inputs["user@pool,5,200:" + *it] = (CephFile){*it, "pool", "user", 5, 200, 4*MB}; - inputs["pool,5,200,800:" + *it] = (CephFile){*it, "pool", "admin", 5, 200, 800}; - inputs["user@pool,5,200,800:" + *it] = (CephFile){*it, "pool", "user", 5, 200, 800}; - } - for (std::map::const_iterator it = inputs.begin(); - it != inputs.end(); - it++) { - std::cout << it->first << std::endl; - checkResult(parseFile(it->first), it->second); - } -} From 06c9432dc6565eda4083f76cbebd9094a418e819 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 15:32:29 +0100 Subject: [PATCH 664/773] [XrdCeph] Update CMake build system to be like other plugins --- .ci/config.cmake | 1 + {src/XrdCeph/cmake => cmake}/Findceph.cmake | 2 +- cmake/XRootDDefaults.cmake | 6 + cmake/XRootDSummary.cmake | 2 +- debian/rules | 4 +- src/CMakeLists.txt | 4 +- src/XrdCeph/.gitattributes | 1 - src/XrdCeph/.gitignore | 58 - src/XrdCeph/.gitlab-ci.yml | 75 - src/XrdCeph/.travis.yml | 22 - src/XrdCeph/CMakeLists.txt | 100 +- src/XrdCeph/COPYING | 674 ------- src/XrdCeph/COPYING.BSD | 35 - src/XrdCeph/COPYING.LGPL | 165 -- src/XrdCeph/Doxyfile | 316 --- src/XrdCeph/LICENSE | 18 - src/XrdCeph/README | 54 - src/XrdCeph/VERSION_INFO | 1 - src/XrdCeph/{src/XrdCeph => }/XrdCephOss.cc | 13 +- src/XrdCeph/{src/XrdCeph => }/XrdCephOss.hh | 2 + .../{src/XrdCeph => }/XrdCephOssDir.cc | 0 .../{src/XrdCeph => }/XrdCephOssDir.hh | 0 .../{src/XrdCeph => }/XrdCephOssFile.cc | 0 .../{src/XrdCeph => }/XrdCephOssFile.hh | 0 src/XrdCeph/{src/XrdCeph => }/XrdCephPosix.cc | 0 src/XrdCeph/{src/XrdCeph => }/XrdCephPosix.hh | 0 src/XrdCeph/{src/XrdCeph => }/XrdCephXAttr.cc | 0 src/XrdCeph/{src/XrdCeph => }/XrdCephXAttr.hh | 0 src/XrdCeph/cmake/FindCppUnit.cmake | 30 - src/XrdCeph/cmake/FindXRootD.cmake | 35 - src/XrdCeph/cmake/GNUInstallDirs.cmake | 182 -- src/XrdCeph/cmake/XRootDDefaults.cmake | 17 - src/XrdCeph/cmake/XRootDFindLibs.cmake | 16 - src/XrdCeph/cmake/XRootDOSDefs.cmake | 44 - src/XrdCeph/cmake/XRootDSummary.cmake | 21 - src/XrdCeph/cmake/XRootDUtils.cmake | 39 - src/XrdCeph/docs/PreReleaseNotes.txt | 8 - src/XrdCeph/docs/ReleaseNotes.txt | 1692 ----------------- src/XrdCeph/packaging/debian/compat | 1 - src/XrdCeph/packaging/debian/control | 48 - src/XrdCeph/packaging/debian/copyright | 18 - src/XrdCeph/packaging/debian/rules | 12 - src/XrdCeph/packaging/debian/source/format | 1 - .../debian/xrootd-client-devel.install | 9 - .../debian/xrootd-client-libs.install | 8 - .../debian/xrootd-client-libs.postinst | 3 - .../debian/xrootd-client-libs.postrm | 3 - .../packaging/debian/xrootd-client.install | 18 - .../packaging/debian/xrootd-devel.install | 15 - .../packaging/debian/xrootd-libs.install | 9 - .../packaging/debian/xrootd-libs.postinst | 3 - .../packaging/debian/xrootd-libs.postrm | 3 - .../debian/xrootd-private-devel.install | 3 - .../debian/xrootd-server-devel.install | 8 - .../debian/xrootd-server-libs.install | 14 - .../debian/xrootd-server-libs.postinst | 3 - .../debian/xrootd-server-libs.postrm | 3 - .../debian_scripts/publish_debian_cern.sh | 35 - src/XrdCeph/packaging/makesrpm.sh | 256 --- .../packaging/rhel/xrootd-ceph.spec.in | 167 -- src/XrdCeph/src/CMakeLists.txt | 10 - src/XrdCeph/src/XrdCeph.cmake | 74 - src/XrdCeph/src/XrdVersion.hh.in | 106 -- tests/CMakeLists.txt | 1 + .../tests => tests/XrdCeph}/CMakeLists.txt | 6 +- .../tests => tests/XrdCeph}/XrdCeph.cc | 0 xrootd.spec | 2 +- 67 files changed, 69 insertions(+), 4407 deletions(-) rename {src/XrdCeph/cmake => cmake}/Findceph.cmake (86%) delete mode 100644 src/XrdCeph/.gitattributes delete mode 100644 src/XrdCeph/.gitignore delete mode 100644 src/XrdCeph/.gitlab-ci.yml delete mode 100644 src/XrdCeph/.travis.yml delete mode 100644 src/XrdCeph/COPYING delete mode 100644 src/XrdCeph/COPYING.BSD delete mode 100644 src/XrdCeph/COPYING.LGPL delete mode 100644 src/XrdCeph/Doxyfile delete mode 100644 src/XrdCeph/LICENSE delete mode 100644 src/XrdCeph/README delete mode 100644 src/XrdCeph/VERSION_INFO rename src/XrdCeph/{src/XrdCeph => }/XrdCephOss.cc (99%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephOss.hh (99%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephOssDir.cc (100%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephOssDir.hh (100%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephOssFile.cc (100%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephOssFile.hh (100%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephPosix.cc (100%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephPosix.hh (100%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephXAttr.cc (100%) rename src/XrdCeph/{src/XrdCeph => }/XrdCephXAttr.hh (100%) delete mode 100644 src/XrdCeph/cmake/FindCppUnit.cmake delete mode 100644 src/XrdCeph/cmake/FindXRootD.cmake delete mode 100644 src/XrdCeph/cmake/GNUInstallDirs.cmake delete mode 100644 src/XrdCeph/cmake/XRootDDefaults.cmake delete mode 100644 src/XrdCeph/cmake/XRootDFindLibs.cmake delete mode 100644 src/XrdCeph/cmake/XRootDOSDefs.cmake delete mode 100644 src/XrdCeph/cmake/XRootDSummary.cmake delete mode 100644 src/XrdCeph/cmake/XRootDUtils.cmake delete mode 100644 src/XrdCeph/docs/PreReleaseNotes.txt delete mode 100644 src/XrdCeph/docs/ReleaseNotes.txt delete mode 100644 src/XrdCeph/packaging/debian/compat delete mode 100644 src/XrdCeph/packaging/debian/control delete mode 100644 src/XrdCeph/packaging/debian/copyright delete mode 100755 src/XrdCeph/packaging/debian/rules delete mode 100644 src/XrdCeph/packaging/debian/source/format delete mode 100644 src/XrdCeph/packaging/debian/xrootd-client-devel.install delete mode 100644 src/XrdCeph/packaging/debian/xrootd-client-libs.install delete mode 100644 src/XrdCeph/packaging/debian/xrootd-client-libs.postinst delete mode 100644 src/XrdCeph/packaging/debian/xrootd-client-libs.postrm delete mode 100644 src/XrdCeph/packaging/debian/xrootd-client.install delete mode 100644 src/XrdCeph/packaging/debian/xrootd-devel.install delete mode 100644 src/XrdCeph/packaging/debian/xrootd-libs.install delete mode 100644 src/XrdCeph/packaging/debian/xrootd-libs.postinst delete mode 100644 src/XrdCeph/packaging/debian/xrootd-libs.postrm delete mode 100644 src/XrdCeph/packaging/debian/xrootd-private-devel.install delete mode 100644 src/XrdCeph/packaging/debian/xrootd-server-devel.install delete mode 100644 src/XrdCeph/packaging/debian/xrootd-server-libs.install delete mode 100644 src/XrdCeph/packaging/debian/xrootd-server-libs.postinst delete mode 100644 src/XrdCeph/packaging/debian/xrootd-server-libs.postrm delete mode 100755 src/XrdCeph/packaging/debian_scripts/publish_debian_cern.sh delete mode 100755 src/XrdCeph/packaging/makesrpm.sh delete mode 100644 src/XrdCeph/packaging/rhel/xrootd-ceph.spec.in delete mode 100644 src/XrdCeph/src/CMakeLists.txt delete mode 100644 src/XrdCeph/src/XrdCeph.cmake delete mode 100644 src/XrdCeph/src/XrdVersion.hh.in rename {src/XrdCeph/tests => tests/XrdCeph}/CMakeLists.txt (79%) rename {src/XrdCeph/tests => tests/XrdCeph}/XrdCeph.cc (100%) diff --git a/.ci/config.cmake b/.ci/config.cmake index 1da3b58ada5..38573e6a7c1 100644 --- a/.ci/config.cmake +++ b/.ci/config.cmake @@ -1,6 +1,7 @@ set(FORCE_ENABLED 0 CACHE BOOL "") set(ENABLE_ASAN 0 CACHE BOOL "") set(ENABLE_TSAN 0 CACHE BOOL "") +set(ENABLE_CEPH 1 CACHE BOOL "") set(ENABLE_FUSE 1 CACHE BOOL "") set(ENABLE_HTTP 1 CACHE BOOL "") set(ENABLE_KRB5 1 CACHE BOOL "") diff --git a/src/XrdCeph/cmake/Findceph.cmake b/cmake/Findceph.cmake similarity index 86% rename from src/XrdCeph/cmake/Findceph.cmake rename to cmake/Findceph.cmake index 661891de850..d90d2879056 100644 --- a/src/XrdCeph/cmake/Findceph.cmake +++ b/cmake/Findceph.cmake @@ -40,4 +40,4 @@ find_library( set(RADOS_LIBS ${RADOS_LIB} ${RADOSSTRIPER_LIB}) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ceph DEFAULT_MSG RADOS_INCLUDE_DIR RADOS_LIBS) +find_package_handle_standard_args(ceph REQUIRED_VARS RADOS_INCLUDE_DIR RADOS_LIB RADOSSTRIPER_LIB) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index f7223ceadd2..4dd9af38f4d 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -12,6 +12,7 @@ endif() include( CMakeDependentOption ) define_default( PLUGIN_VERSION 5 ) +option( ENABLE_CEPH "Enable XrdCeph plugins." FALSE ) option( ENABLE_FUSE "Enable the fuse filesystem driver if possible." TRUE ) option( ENABLE_KRB5 "Enable the Kerberos 5 authentication if possible." TRUE ) option( ENABLE_READLINE "Enable the lib readline support in the commandline utilities." TRUE ) @@ -32,3 +33,8 @@ cmake_dependent_option( ENABLE_MACAROONS "Enable Macaroons plugin." TRUE "NOT XR option( FORCE_ENABLED "Fail build if enabled components cannot be built." FALSE ) cmake_dependent_option( USE_SYSTEM_ISAL "Use isa-l installed in the system" FALSE "ENABLE_XRDEC" FALSE ) define_default( XRD_PYTHON_REQ_VERSION 3 ) + +# backward compatibility +if(XRDCEPH_SUBMODULE) + set(ENABLE_CEPH TRUE) +endif() diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index 7df55dda4e5..c7bd4db2f37 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -2,7 +2,7 @@ # Print the configuration summary #------------------------------------------------------------------------------- set( TRUE_VAR TRUE ) -component_status( CEPH XRDCEPH_SUBMODULE TRUE_VAR) +component_status( CEPH ENABLE_CEPH BUILD_CEPH ) component_status( FUSE BUILD_FUSE FUSE_FOUND ) component_status( HTTP BUILD_HTTP OPENSSL_FOUND ) component_status( KRB5 BUILD_KRB5 KERBEROS5_FOUND ) diff --git a/debian/rules b/debian/rules index 479f83d1fdc..ee90c9e4f9c 100755 --- a/debian/rules +++ b/debian/rules @@ -10,6 +10,7 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all optimize=-lto override_dh_auto_configure: dh_auto_configure -- \ + -DENABLE_CEPH:BOOL=1 \ -DENABLE_FUSE:BOOL=1 \ -DENABLE_HTTP:BOOL=1 \ -DENABLE_KRB5:BOOL=1 \ @@ -23,8 +24,7 @@ override_dh_auto_configure: -DENABLE_TESTS:BOOL=1 \ -DFORCE_ENABLED:BOOL=1 \ -DINSTALL_PYTHON_BINDINGS:BOOL=0 \ - -DUSE_SYSTEM_ISAL:BOOL=1 \ - -DXRDCEPH_SUBMODULE:BOOL=1 + -DUSE_SYSTEM_ISAL:BOOL=1 override_dh_auto_build: dh_auto_build diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69add8bd96f..39d7ae578b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,9 +70,7 @@ if( NOT XRDCL_ONLY ) include( XrdVoms ) endif() - if( XRDCEPH_SUBMODULE ) - add_subdirectory( XrdCeph ) - endif() + add_subdirectory( XrdCeph ) if( BUILD_SCITOKENS ) include( XrdSciTokens ) diff --git a/src/XrdCeph/.gitattributes b/src/XrdCeph/.gitattributes deleted file mode 100644 index 0893fe6afee..00000000000 --- a/src/XrdCeph/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -VERSION_INFO export-subst diff --git a/src/XrdCeph/.gitignore b/src/XrdCeph/.gitignore deleted file mode 100644 index 9bf47afb3b4..00000000000 --- a/src/XrdCeph/.gitignore +++ /dev/null @@ -1,58 +0,0 @@ -*.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 -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 - diff --git a/src/XrdCeph/.gitlab-ci.yml b/src/XrdCeph/.gitlab-ci.yml deleted file mode 100644 index 3b097867e8e..00000000000 --- a/src/XrdCeph/.gitlab-ci.yml +++ /dev/null @@ -1,75 +0,0 @@ -stages: - - build:rpm - -release: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 - - git checkout tags/${CI_COMMIT_TAG} - - cd packaging/ - - ./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" -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 - - sudo -u stci -H cp RPMS/* $repo - - sudo -u stci -H createrepo --update -q $repo - tags: - - docker_node - 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/ - - 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: - - 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/ - - 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 - - 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 - diff --git a/src/XrdCeph/.travis.yml b/src/XrdCeph/.travis.yml deleted file mode 100644 index 0f57d7cb54e..00000000000 --- a/src/XrdCeph/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -sudo: false -dist: trusty -addons: - apt: - packages: - - libxml2-dev - - libcppunit-dev -language: cpp -compiler: - - clang - - gcc -script: - - mkdir build - - pushd build - - cmake -DCMAKE_INSTALL_PREFIX=$HOME/xrootd -DENABLE_TESTS=1 .. - - make - - make install - - popd -#after_script: -# - pushd build -# - ./tests/common/text-runner ./tests/XrdClTests/libXrdClTests.so 'All Tests' -# - popd diff --git a/src/XrdCeph/CMakeLists.txt b/src/XrdCeph/CMakeLists.txt index c8730143079..ff63a0526b9 100644 --- a/src/XrdCeph/CMakeLists.txt +++ b/src/XrdCeph/CMakeLists.txt @@ -1,62 +1,52 @@ -#------------------------------------------------------------------------------- -# Project description -#------------------------------------------------------------------------------- -cmake_minimum_required(VERSION 3.16...3.25) - -project( xrootd-ceph ) - -set( CMAKE_MODULE_PATH - ${PROJECT_SOURCE_DIR}/src - ${PROJECT_SOURCE_DIR}/cmake ) +if(NOT ENABLE_CEPH) + unset(BUILD_CEPH CACHE) + return() +endif() -if( NOT XRDCEPH_SUBMODULE ) - if(NOT (CMAKE_VERSION VERSION_LESS "3.1")) - cmake_policy(SET CMP0054 OLD) +if(FORCE_ENABLED) + find_package(ceph REQUIRED) +else() + find_package(ceph) + if(NOT CEPH_FOUND) + unset(BUILD_CEPH CACHE) + return() endif() endif() -include( XRootDUtils ) -CheckBuildDirectory() - -include( XRootDOSDefs ) -include( XRootDDefaults ) -include( XRootDFindLibs ) - -add_definitions( -DXRDPLUGIN_SOVERSION="${PLUGIN_VERSION}" ) - -#------------------------------------------------------------------------------- -# Generate the version header -#------------------------------------------------------------------------------- -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() +set(BUILD_CEPH TRUE CACHE BOOL INTERNAL FORCE) -#------------------------------------------------------------------------------- -# Build in subdirectories -#------------------------------------------------------------------------------- -add_subdirectory( src ) +add_library(XrdCephPosix SHARED + XrdCephPosix.cc XrdCephPosix.hh) -if( BUILD_TESTS ) - ENABLE_TESTING() - add_subdirectory( tests ) -endif() +target_compile_options(XrdCephPosix + PRIVATE -Wno-deprecated-declarations) + +target_link_libraries(XrdCephPosix + PRIVATE XrdUtils ${RADOS_LIBS}) + +target_include_directories(XrdCephPosix + PUBLIC ${RADOS_INCLUDE_DIR} $) + +set_target_properties(XrdCephPosix + PROPERTIES VERSION 0.0.1 SOVERSION 0) + +set(LIB_XRD_CEPH XrdCeph-${PLUGIN_VERSION}) + +add_library(${LIB_XRD_CEPH} MODULE + XrdCephOss.cc XrdCephOss.hh + XrdCephOssFile.cc XrdCephOssFile.hh + XrdCephOssDir.cc XrdCephOssDir.hh) + +target_link_libraries(${LIB_XRD_CEPH} + PRIVATE ${XROOTD_LIBRARIES} XrdCephPosix) + +set(LIB_XRD_CEPH_XATTR XrdCephXattr-${PLUGIN_VERSION}) + +add_library(${LIB_XRD_CEPH_XATTR} MODULE + XrdCephXAttr.cc XrdCephXAttr.hh) + +target_link_libraries(${LIB_XRD_CEPH_XATTR} + PRIVATE ${XROOTD_LIBRARIES} XrdCephPosix) -include( XRootDSummary ) +install(TARGETS XrdCephPosix ${LIB_XRD_CEPH} ${LIB_XRD_CEPH_XATTR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/XrdCeph/COPYING b/src/XrdCeph/COPYING deleted file mode 100644 index 94a9ed024d3..00000000000 --- a/src/XrdCeph/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that 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 General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/src/XrdCeph/COPYING.BSD b/src/XrdCeph/COPYING.BSD deleted file mode 100644 index ea8ff31a74e..00000000000 --- a/src/XrdCeph/COPYING.BSD +++ /dev/null @@ -1,35 +0,0 @@ -******************************************************************************** -*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.* -* See files LICENSE, COPYING.LGPL, and COPYING for license details. * -******************************************************************************** - -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. - 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. diff --git a/src/XrdCeph/COPYING.LGPL b/src/XrdCeph/COPYING.LGPL deleted file mode 100644 index 65c5ca88a67..00000000000 --- a/src/XrdCeph/COPYING.LGPL +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/src/XrdCeph/Doxyfile b/src/XrdCeph/Doxyfile deleted file mode 100644 index 0531e916456..00000000000 --- a/src/XrdCeph/Doxyfile +++ /dev/null @@ -1,316 +0,0 @@ -# Doxyfile 1.3.7 - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- -PROJECT_NAME = xrootd -PROJECT_NUMBER = -OUTPUT_DIRECTORY = doxydoc -CREATE_SUBDIRS = NO -OUTPUT_LANGUAGE = English -USE_WINDOWS_ENCODING = NO -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = NO -STRIP_FROM_PATH = -STRIP_FROM_INC_PATH = -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = NO -DETAILS_AT_TOP = NO -INHERIT_DOCS = YES -DISTRIBUTE_GROUP_DOC = NO -TAB_SIZE = 8 -ALIASES = -OPTIMIZE_OUTPUT_FOR_C = NO -OPTIMIZE_OUTPUT_JAVA = NO -SUBGROUPING = YES -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- -EXTRACT_ALL = YES -EXTRACT_PRIVATE = YES -EXTRACT_STATIC = YES -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -HIDE_UNDOC_MEMBERS = NO -HIDE_UNDOC_CLASSES = NO -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = YES -HIDE_SCOPE_NAMES = NO -SHOW_INCLUDE_FILES = YES -INLINE_INFO = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_BY_SCOPE_NAME = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- -INPUT = \ -src/XrdSec/XrdSecEntity.hh \ -src/XrdSec/XrdSecInterface.hh \ -src/Xrd/XrdJob.hh \ -src/Xrd/XrdBuffer.hh \ -src/Xrd/XrdScheduler.hh \ -src/Xrd/XrdLink.hh \ -src/Xrd/XrdLinkMatch.hh \ -src/Xrd/XrdProtocol.hh \ -src/XrdXrootd/XrdXrootdMonData.hh \ -src/XrdVersionPlugin.hh \ -src/XrdCks/XrdCksData.hh \ -src/XrdCks/XrdCksManager.hh \ -src/XrdCks/XrdCksCalc.hh \ -src/XrdCks/XrdCks.hh \ -src/XrdSfs/XrdSfsInterface.hh \ -src/XrdSfs/XrdSfsAio.hh \ -src/XrdNet/XrdNet.hh \ -src/XrdNet/XrdNetCmsNotify.hh \ -src/XrdNet/XrdNetConnect.hh \ -src/XrdNet/XrdNetOpts.hh \ -src/XrdNet/XrdNetSocket.hh \ -src/XrdSys/XrdSysError.hh \ -src/XrdSys/XrdSysPlatform.hh \ -src/XrdSys/XrdSysLogger.hh \ -src/XrdSys/XrdSysPthread.hh \ -src/XrdSys/XrdSysTimer.hh \ -src/XrdSys/XrdSysHeaders.hh \ -src/XrdSys/XrdSysDNS.hh \ -src/XrdSys/XrdSysXSLock.hh \ -src/XrdSys/XrdSysIOEvents.hh \ -src/XrdSys/XrdSysAtomics.hh \ -src/XrdSys/XrdSysPlugin.hh \ -src/XrdSys/XrdSysSemWait.hh \ -src/XrdClient/XrdClientConst.hh \ -src/XrdClient/XrdClientVector.hh \ -src/XrdClient/XrdClientAbs.hh \ -src/XrdClient/XrdClientAbsMonIntf.hh \ -src/XrdClient/XrdClient.hh \ -src/XrdClient/XrdClientUnsolMsg.hh \ -src/XrdClient/XrdClientAdmin.hh \ -src/XrdClient/XrdClientUrlSet.hh \ -src/XrdClient/XrdClientUrlInfo.hh \ -src/XrdClient/XrdClientEnv.hh \ -src/XrdOuc/XrdOucRash.hh \ -src/XrdOuc/XrdOucStream.hh \ -src/XrdOuc/XrdOuca2x.hh \ -src/XrdOuc/XrdOucTrace.hh \ -src/XrdOuc/XrdOucCRC.hh \ -src/XrdOuc/XrdOucErrInfo.hh \ -src/XrdOuc/XrdOucDLlist.hh \ -src/XrdOuc/XrdOucCache.hh \ -src/XrdOuc/XrdOucTList.hh \ -src/XrdOuc/XrdOucName2Name.hh \ -src/XrdOuc/XrdOucTable.hh \ -src/XrdOuc/XrdOucIOVec.hh \ -src/XrdOuc/XrdOucCallBack.hh \ -src/XrdOuc/XrdOucEnum.hh \ -src/XrdOuc/XrdOucLock.hh \ -src/XrdOuc/XrdOucTokenizer.hh \ -src/XrdOuc/XrdOucEnv.hh \ -src/XrdOuc/XrdOucString.hh \ -src/XrdOuc/XrdOucChain.hh \ -src/XrdOuc/XrdOucUtils.hh \ -src/XrdOuc/XrdOucHash.hh \ -src/XrdOss/XrdOss.hh \ -src/XrdOss/XrdOssStatInfo.hh \ -src/XrdOss/XrdOssDefaultSS.hh \ -src/XrdPosix/XrdPosixXrootd.hh \ -src/XrdPosix/XrdPosixExtern.hh \ -src/XrdPosix/XrdPosixXrootdPath.hh \ -src/XrdPosix/XrdPosixOsDep.hh \ -src/XrdPosix/XrdPosixCallBack.hh \ -src/XrdAcc/XrdAccAuthorize.hh \ -src/XrdAcc/XrdAccPrivs.hh \ -src/XProtocol/XPtypes.hh \ -src/XProtocol/XProtocol.hh \ -src/XrdCms/XrdCmsClient.hh \ -src/XrdCl/XrdClEnv.hh \ -src/XrdCl/XrdClPostMaster.hh \ -src/XrdCl/XrdClFileSystem.hh \ -src/XrdCl/XrdClPostMasterInterfaces.hh \ -src/XrdCl/XrdClBuffer.hh \ -src/XrdCl/XrdClConstants.hh \ -src/XrdCl/XrdClCopyProcess.hh \ -src/XrdCl/XrdClDefaultEnv.hh \ -src/XrdCl/XrdClMessage.hh \ -src/XrdCl/XrdClMonitor.hh \ -src/XrdCl/XrdClStatus.hh \ -src/XrdCl/XrdClTransportManager.hh \ -src/XrdCl/XrdClURL.hh \ -src/XrdCl/XrdClAnyObject.hh \ -src/XrdCl/XrdClXRootDResponses.hh \ -src/XrdCl/XrdClFile.hh \ -src/XrdFileCache/XrdFileCacheDecision.hh -src/XrdFileCache/XrdFileCacheFile.hh -src/XrdFileCache/XrdFileCache.hh -src/XrdFileCache/XrdFileCacheInfo.hh -src/XrdFileCache/XrdFileCacheIOEntireFile.hh -src/XrdFileCache/XrdFileCacheIOFileBlock.hh -src/XrdFileCache/XrdFileCacheIO.hh -src/XrdFileCache/XrdFileCachePrint.hh -src/XrdFileCache/XrdFileCacheStats.hh -src/XrdFileCache/XrdFileCacheTrace.hh - -FILE_PATTERNS = *.hh -RECURSIVE = YES -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXAMPLE_PATH = -EXAMPLE_PATTERNS = -EXAMPLE_RECURSIVE = NO -IMAGE_PATH = -INPUT_FILTER = -FILTER_SOURCE_FILES = NO -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- -SOURCE_BROWSER = NO -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = YES -REFERENCES_RELATION = YES -VERBATIM_HEADERS = YES -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- -ALPHABETICAL_INDEX = NO -COLS_IN_ALPHA_INDEX = 5 -IGNORE_PREFIX = -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_ALIGN_MEMBERS = YES -GENERATE_HTMLHELP = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -BINARY_TOC = NO -TOC_EXPAND = NO -DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 4 -GENERATE_TREEVIEW = NO -TREEVIEW_WIDTH = 250 -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- -GENERATE_LATEX = NO -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4wide -EXTRA_PACKAGES = -LATEX_HEADER = -PDF_HYPERLINKS = NO -USE_PDFLATEX = NO -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- -GENERATE_MAN = NO -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_LINKS = NO -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- -GENERATE_XML = NO -XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = -XML_PROGRAMLISTING = YES -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- -GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- -TAGFILES = -GENERATE_TAGFILE = -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -PERL_PATH = /usr/bin/perl -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- -CLASS_DIAGRAMS = YES -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = YES -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -UML_LOOK = NO -TEMPLATE_RELATIONS = NO -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DOT_IMAGE_FORMAT = png -DOT_PATH = -DOTFILE_DIRS = -MAX_DOT_GRAPH_WIDTH = 1024 -MAX_DOT_GRAPH_HEIGHT = 1024 -MAX_DOT_GRAPH_DEPTH = 0 -GENERATE_LEGEND = YES -DOT_CLEANUP = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- -SEARCHENGINE = NO diff --git a/src/XrdCeph/LICENSE b/src/XrdCeph/LICENSE deleted file mode 100644 index 8fd5621be2c..00000000000 --- a/src/XrdCeph/LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -"Copyright (c) 2005-2012, Board of Trustees of the Leland Stanford, Jr. University.\n" -"Produced under contract DE-AC02-76-SF00515 with the US Department of Energy. \n" -"All rights reserved. The copyright holder's institutional names may not be used to\n" -"endorse or promote products derived from this software without specific prior \n" -"written permission.\n\n" -"This file is part of the XRootD software suite. \n\n" -"XRootD is free software: you can redistribute it and/or modify it under the terms \n" -"of the GNU Lesser General Public License as published by the Free Software \n" -"Foundation, either version 3 of the License, or (at your option) any later version.\n\n" -"XRootD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n" -"without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR \n" -"PURPOSE. See the GNU Lesser General Public License for more details. \nn" -"You should have received a copy of the GNU Lesser General Public License along \n" -"with XRootD in a file called COPYING.LESSER (LGPL license) and file COPYING (GPL \n" -"license). If not, see .\n\n" -"Prior to September 2nd, 2012 the XRootD software suite was licensed under a\n" -"modified BSD license (see file COPYING.BSD). This applies to all code that\n" -"was in the XRootD git repository prior to that date.\n" diff --git a/src/XrdCeph/README b/src/XrdCeph/README deleted file mode 100644 index e2bf17c3df6..00000000000 --- a/src/XrdCeph/README +++ /dev/null @@ -1,54 +0,0 @@ - --------------------------------------------------------------------------------- - _ _ ______ _____ - \ \ / (_____ \ _ (____ \ - \ \/ / _____) ) ___ ___ | |_ _ \ \ - ) ( (_____ ( / _ \ / _ \| _)| | | | - / /\ \ | | |_| | |_| | |__| |__/ / - /_/ \_\ |_|\___/ \___/ \___)_____/ - --------------------------------------------------------------------------------- - -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 7 and derivatives (Scientific Linux) - compiled with gcc - -2. B U I L D I N S T R U C T I O N S - -2.1 Build system - - xrootd-ceph uses CMake to handle the build process. Please use CMake version 3 or greater (e.g. cmake3). - -2.2 Build steps - - * Create an empty build directory: - - 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-ceph/source -DCMAKE_INSTALL_PREFIX=/opt/xrootd - - * Build the source: - - make - - * Install the shared libraries: - - make install diff --git a/src/XrdCeph/VERSION_INFO b/src/XrdCeph/VERSION_INFO deleted file mode 100644 index 5ac45999939..00000000000 --- a/src/XrdCeph/VERSION_INFO +++ /dev/null @@ -1 +0,0 @@ -$Format:RefNames: %d%nShortHash: %h%nDate: %ai%n$ \ No newline at end of file diff --git a/src/XrdCeph/src/XrdCeph/XrdCephOss.cc b/src/XrdCeph/XrdCephOss.cc similarity index 99% rename from src/XrdCeph/src/XrdCeph/XrdCephOss.cc rename to src/XrdCeph/XrdCephOss.cc index 5a9ada4f51b..dc707a02771 100644 --- a/src/XrdCeph/src/XrdCeph/XrdCephOss.cc +++ b/src/XrdCeph/XrdCephOss.cc @@ -26,21 +26,18 @@ #include #include +#include "XrdVersion.hh" +#include "XrdCeph/XrdCephOss.hh" +#include "XrdCeph/XrdCephOssDir.hh" +#include "XrdCeph/XrdCephOssFile.hh" #include "XrdCeph/XrdCephPosix.hh" + #include "XrdOuc/XrdOucEnv.hh" #include "XrdSys/XrdSysError.hh" #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" -#include "XrdCeph/XrdCephOssFile.hh" XrdVERSIONINFO(XrdOssGetStorageSystem, XrdCephOss); diff --git a/src/XrdCeph/src/XrdCeph/XrdCephOss.hh b/src/XrdCeph/XrdCephOss.hh similarity index 99% rename from src/XrdCeph/src/XrdCeph/XrdCephOss.hh rename to src/XrdCeph/XrdCephOss.hh index 838030dc3db..80cb1a25646 100644 --- a/src/XrdCeph/src/XrdCeph/XrdCephOss.hh +++ b/src/XrdCeph/XrdCephOss.hh @@ -26,7 +26,9 @@ #define __CEPH_OSS_HH__ #include + #include +#include //------------------------------------------------------------------------------ //! This class implements XrdOss interface for usage with a CEPH storage. diff --git a/src/XrdCeph/src/XrdCeph/XrdCephOssDir.cc b/src/XrdCeph/XrdCephOssDir.cc similarity index 100% rename from src/XrdCeph/src/XrdCeph/XrdCephOssDir.cc rename to src/XrdCeph/XrdCephOssDir.cc diff --git a/src/XrdCeph/src/XrdCeph/XrdCephOssDir.hh b/src/XrdCeph/XrdCephOssDir.hh similarity index 100% rename from src/XrdCeph/src/XrdCeph/XrdCephOssDir.hh rename to src/XrdCeph/XrdCephOssDir.hh diff --git a/src/XrdCeph/src/XrdCeph/XrdCephOssFile.cc b/src/XrdCeph/XrdCephOssFile.cc similarity index 100% rename from src/XrdCeph/src/XrdCeph/XrdCephOssFile.cc rename to src/XrdCeph/XrdCephOssFile.cc diff --git a/src/XrdCeph/src/XrdCeph/XrdCephOssFile.hh b/src/XrdCeph/XrdCephOssFile.hh similarity index 100% rename from src/XrdCeph/src/XrdCeph/XrdCephOssFile.hh rename to src/XrdCeph/XrdCephOssFile.hh diff --git a/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc similarity index 100% rename from src/XrdCeph/src/XrdCeph/XrdCephPosix.cc rename to src/XrdCeph/XrdCephPosix.cc diff --git a/src/XrdCeph/src/XrdCeph/XrdCephPosix.hh b/src/XrdCeph/XrdCephPosix.hh similarity index 100% rename from src/XrdCeph/src/XrdCeph/XrdCephPosix.hh rename to src/XrdCeph/XrdCephPosix.hh diff --git a/src/XrdCeph/src/XrdCeph/XrdCephXAttr.cc b/src/XrdCeph/XrdCephXAttr.cc similarity index 100% rename from src/XrdCeph/src/XrdCeph/XrdCephXAttr.cc rename to src/XrdCeph/XrdCephXAttr.cc diff --git a/src/XrdCeph/src/XrdCeph/XrdCephXAttr.hh b/src/XrdCeph/XrdCephXAttr.hh similarity index 100% rename from src/XrdCeph/src/XrdCeph/XrdCephXAttr.hh rename to src/XrdCeph/XrdCephXAttr.hh diff --git a/src/XrdCeph/cmake/FindCppUnit.cmake b/src/XrdCeph/cmake/FindCppUnit.cmake deleted file mode 100644 index cc6e7400191..00000000000 --- a/src/XrdCeph/cmake/FindCppUnit.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# Try to find CPPUnit -# Once done, this will define -# -# CPPUNIT_FOUND - system has cppunit -# CPPUNIT_INCLUDE_DIRS - the cppunit include directories -# CPPUNIT_LIBRARIES - cppunit libraries directories - -find_path( CPPUNIT_INCLUDE_DIRS cppunit/ui/text/TestRunner.h - HINTS - ${CPPUNIT_DIR} - $ENV{CPPUNIT_DIR} - /usr - /opt - PATH_SUFFIXES include -) - -find_library( CPPUNIT_LIBRARIES cppunit - HINTS - ${CPPUNIT_DIR} - $ENV{CPPUNIT_DIR} - /usr - /opt - PATH_SUFFIXES lib -) - -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) diff --git a/src/XrdCeph/cmake/FindXRootD.cmake b/src/XrdCeph/cmake/FindXRootD.cmake deleted file mode 100644 index a3596ebc338..00000000000 --- a/src/XrdCeph/cmake/FindXRootD.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# 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 - -if( XRDCEPH_SUBMODULE ) - set( XROOTD_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src ) - set( XROOTD_LIBRARIES XrdUtils ) -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 - ) -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/XrdCeph/cmake/GNUInstallDirs.cmake b/src/XrdCeph/cmake/GNUInstallDirs.cmake deleted file mode 100644 index a114dcb2e18..00000000000 --- a/src/XrdCeph/cmake/GNUInstallDirs.cmake +++ /dev/null @@ -1,182 +0,0 @@ -# - Define GNU standard installation directories -# Provides install directory variables as defined for GNU software: -# http://www.gnu.org/prep/standards/html_node/Directory-Variables.html -# Inclusion of this module defines the following variables: -# CMAKE_INSTALL_

    - destination for files of a given type -# CMAKE_INSTALL_FULL_ - corresponding absolute path -# where is one of: -# BINDIR - user executables (bin) -# SBINDIR - system admin executables (sbin) -# LIBEXECDIR - program executables (libexec) -# SYSCONFDIR - read-only single-machine data (etc) -# SHAREDSTATEDIR - modifiable architecture-independent data (com) -# LOCALSTATEDIR - modifiable single-machine data (var) -# LIBDIR - object code libraries (lib or lib64) -# INCLUDEDIR - C header files (include) -# OLDINCLUDEDIR - C header files for non-gcc (/usr/include) -# DATAROOTDIR - read-only architecture-independent data root (share) -# DATADIR - read-only architecture-independent data (DATAROOTDIR) -# INFODIR - info documentation (DATAROOTDIR/info) -# LOCALEDIR - locale-dependent data (DATAROOTDIR/locale) -# MANDIR - man documentation (DATAROOTDIR/man) -# DOCDIR - documentation root (DATAROOTDIR/doc/PROJECT_NAME) -# Each CMAKE_INSTALL_ value may be passed to the DESTINATION options of -# install() commands for the corresponding file type. If the includer does -# not define a value the above-shown default will be used and the value will -# appear in the cache for editing by the user. -# Each CMAKE_INSTALL_FULL_ value contains an absolute path constructed -# from the corresponding destination by prepending (if necessary) the value -# of CMAKE_INSTALL_PREFIX. - -#============================================================================= -# Copyright 2011 Nikita Krupen'ko -# Copyright 2011 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -# Installation directories -# -if(NOT DEFINED CMAKE_INSTALL_BINDIR) - set(CMAKE_INSTALL_BINDIR "bin" CACHE PATH "user executables (bin)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SBINDIR) - set(CMAKE_INSTALL_SBINDIR "sbin" CACHE PATH "system admin executables (sbin)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LIBEXECDIR) - set(CMAKE_INSTALL_LIBEXECDIR "libexec" CACHE PATH "program executables (libexec)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SYSCONFDIR) - set(CMAKE_INSTALL_SYSCONFDIR "etc" CACHE PATH "read-only single-machine data (etc)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SHAREDSTATEDIR) - set(CMAKE_INSTALL_SHAREDSTATEDIR "com" CACHE PATH "modifiable architecture-independent data (com)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LOCALSTATEDIR) - set(CMAKE_INSTALL_LOCALSTATEDIR "var" CACHE PATH "modifiable single-machine data (var)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LIBDIR) - set(_LIBDIR_DEFAULT "lib") - # Override this default 'lib' with 'lib64' iff: - # - we are on Linux system but NOT cross-compiling - # - we are NOT on debian - # - we are on a 64 bits system - # reason is: amd64 ABI: http://www.x86-64.org/documentation/abi.pdf - # Note that the future of multi-arch handling may be even - # more complicated than that: http://wiki.debian.org/Multiarch - if(CMAKE_SYSTEM_NAME MATCHES "Linux" - AND NOT CMAKE_CROSSCOMPILING - AND NOT EXISTS "/etc/debian_version") - if(NOT DEFINED CMAKE_SIZEOF_VOID_P) - message(AUTHOR_WARNING - "Unable to determine default CMAKE_INSTALL_LIBDIR directory because no target architecture is known. " - "Please enable at least one language before including GNUInstallDirs.") - else() - if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(_LIBDIR_DEFAULT "lib64") - endif() - endif() - endif() - set(CMAKE_INSTALL_LIBDIR "${_LIBDIR_DEFAULT}" CACHE PATH "object code libraries (${_LIBDIR_DEFAULT})") -endif() - -if(NOT DEFINED CMAKE_INSTALL_INCLUDEDIR) - set(CMAKE_INSTALL_INCLUDEDIR "include" CACHE PATH "C header files (include)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_OLDINCLUDEDIR) - set(CMAKE_INSTALL_OLDINCLUDEDIR "/usr/include" CACHE PATH "C header files for non-gcc (/usr/include)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_DATAROOTDIR) - set(CMAKE_INSTALL_DATAROOTDIR "share" CACHE PATH "read-only architecture-independent data root (share)") -endif() - -#----------------------------------------------------------------------------- -# Values whose defaults are relative to DATAROOTDIR. Store empty values in -# the cache and store the defaults in local variables if the cache values are -# not set explicitly. This auto-updates the defaults as DATAROOTDIR changes. - -if(NOT CMAKE_INSTALL_DATADIR) - set(CMAKE_INSTALL_DATADIR "" CACHE PATH "read-only architecture-independent data (DATAROOTDIR)") - set(CMAKE_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}") -endif() - -if(NOT CMAKE_INSTALL_INFODIR) - set(CMAKE_INSTALL_INFODIR "" CACHE PATH "info documentation (DATAROOTDIR/info)") - set(CMAKE_INSTALL_INFODIR "${CMAKE_INSTALL_DATAROOTDIR}/info") -endif() - -if(NOT CMAKE_INSTALL_LOCALEDIR) - set(CMAKE_INSTALL_LOCALEDIR "" CACHE PATH "locale-dependent data (DATAROOTDIR/locale)") - set(CMAKE_INSTALL_LOCALEDIR "${CMAKE_INSTALL_DATAROOTDIR}/locale") -endif() - -if(NOT CMAKE_INSTALL_MANDIR) - set(CMAKE_INSTALL_MANDIR "" CACHE PATH "man documentation (DATAROOTDIR/man)") - set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_DATAROOTDIR}/man") -endif() - -if(NOT CMAKE_INSTALL_DOCDIR) - set(CMAKE_INSTALL_DOCDIR "" CACHE PATH "documentation root (DATAROOTDIR/doc/PROJECT_NAME)") - set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}") -endif() - -#----------------------------------------------------------------------------- - -mark_as_advanced( - CMAKE_INSTALL_BINDIR - CMAKE_INSTALL_SBINDIR - CMAKE_INSTALL_LIBEXECDIR - CMAKE_INSTALL_SYSCONFDIR - CMAKE_INSTALL_SHAREDSTATEDIR - CMAKE_INSTALL_LOCALSTATEDIR - CMAKE_INSTALL_LIBDIR - CMAKE_INSTALL_INCLUDEDIR - CMAKE_INSTALL_OLDINCLUDEDIR - CMAKE_INSTALL_DATAROOTDIR - CMAKE_INSTALL_DATADIR - CMAKE_INSTALL_INFODIR - CMAKE_INSTALL_LOCALEDIR - CMAKE_INSTALL_MANDIR - CMAKE_INSTALL_DOCDIR - ) - -# Result directories -# -foreach(dir - BINDIR - SBINDIR - LIBEXECDIR - SYSCONFDIR - SHAREDSTATEDIR - LOCALSTATEDIR - LIBDIR - INCLUDEDIR - OLDINCLUDEDIR - DATAROOTDIR - DATADIR - INFODIR - LOCALEDIR - MANDIR - DOCDIR - ) - if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_${dir}}) - set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_${dir}}") - else() - set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_${dir}}") - endif() -endforeach() diff --git a/src/XrdCeph/cmake/XRootDDefaults.cmake b/src/XrdCeph/cmake/XRootDDefaults.cmake deleted file mode 100644 index 15416a3046d..00000000000 --- a/src/XrdCeph/cmake/XRootDDefaults.cmake +++ /dev/null @@ -1,17 +0,0 @@ -#------------------------------------------------------------------------------- -# Define the default build parameters -#------------------------------------------------------------------------------- -if( "${CMAKE_BUILD_TYPE}" STREQUAL "" ) - if( Solaris AND NOT SUNCC_CAN_DO_OPTS ) - set( CMAKE_BUILD_TYPE Debug ) - else() - set( CMAKE_BUILD_TYPE RelWithDebInfo ) - endif() -endif() - -if( NOT XRDCEPH_SUBMODULE ) - define_default( PLUGIN_VERSION 5 ) -endif() - -define_default( ENABLE_TESTS FALSE ) -define_default( ENABLE_CEPH TRUE ) diff --git a/src/XrdCeph/cmake/XRootDFindLibs.cmake b/src/XrdCeph/cmake/XRootDFindLibs.cmake deleted file mode 100644 index 78570502ca1..00000000000 --- a/src/XrdCeph/cmake/XRootDFindLibs.cmake +++ /dev/null @@ -1,16 +0,0 @@ -#------------------------------------------------------------------------------- -# Find the required libraries -#------------------------------------------------------------------------------- - -find_package( XRootD REQUIRED ) - -find_package( ceph REQUIRED ) - -if( ENABLE_TESTS ) - find_package( CppUnit ) - if( CPPUNIT_FOUND ) - set( BUILD_TESTS TRUE ) - else() - set( BUILD_TESTS FALSE ) - endif() -endif() diff --git a/src/XrdCeph/cmake/XRootDOSDefs.cmake b/src/XrdCeph/cmake/XRootDOSDefs.cmake deleted file mode 100644 index eadc24952b7..00000000000 --- a/src/XrdCeph/cmake/XRootDOSDefs.cmake +++ /dev/null @@ -1,44 +0,0 @@ -#------------------------------------------------------------------------------- -# Define the OS variables -#------------------------------------------------------------------------------- - -include( CheckCXXSourceRuns ) - -add_definitions( -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 ) -set( LIBRARY_PATH_PREFIX "lib" ) - -#------------------------------------------------------------------------------- -# GCC -#------------------------------------------------------------------------------- -if( CMAKE_COMPILER_IS_GNUCXX ) - 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 - execute_process( COMMAND ${CMAKE_C_COMPILER} -dumpversion - OUTPUT_VARIABLE GCC_VERSION ) - if( (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1) - AND GCC_VERSION VERSION_LESS 4.2 ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing" ) - endif() - - # for 4.9.3 or greater the 'omit-frame-pointer' - # interfears with custom semaphore implementation - if( (GCC_VERSION VERSION_GREATER 4.9.2) AND (USE_LIBC_SEMAPHORE EQUAL 0) ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer" ) - endif() - - # gcc 6.0 is more pedantic - if( GCC_VERSION VERSION_GREATER 6.0 OR GCC_VERSION VERSION_EQUAL 6.0 ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=misleading-indentation" ) - endif() -endif() - -#------------------------------------------------------------------------------- -# Linux -#------------------------------------------------------------------------------- -set( Linux TRUE ) -include( GNUInstallDirs ) -add_definitions( -D__linux__=1 ) -set( EXTRA_LIBS rt ) - diff --git a/src/XrdCeph/cmake/XRootDSummary.cmake b/src/XrdCeph/cmake/XRootDSummary.cmake deleted file mode 100644 index d8294d37c2f..00000000000 --- a/src/XrdCeph/cmake/XRootDSummary.cmake +++ /dev/null @@ -1,21 +0,0 @@ -#------------------------------------------------------------------------------- -# Print the configuration summary -#------------------------------------------------------------------------------- -set( TRUE_VAR TRUE ) -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} ) -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: " ${STATUS_CEPH} ) -message( STATUS "XRootD: " ${STATUS_XROOTD} ) -message( STATUS "Tests: " ${STATUS_TESTS} ) -message( STATUS "----------------------------------------" ) -endif() diff --git a/src/XrdCeph/cmake/XRootDUtils.cmake b/src/XrdCeph/cmake/XRootDUtils.cmake deleted file mode 100644 index 6fbdf0fc9e7..00000000000 --- a/src/XrdCeph/cmake/XRootDUtils.cmake +++ /dev/null @@ -1,39 +0,0 @@ - -#------------------------------------------------------------------------------- -# Add a compiler define flag if a variable is defined -#------------------------------------------------------------------------------- -macro( define_default variable value ) - if( NOT DEFINED ${variable} ) - set( ${variable} ${value} ) - endif() -endmacro() - -macro( component_status name flag found ) - if( ${flag} AND ${found} ) - set( STATUS_${name} "yes" ) - elseif( ${flag} AND NOT ${found} ) - set( STATUS_${name} "libs not found" ) - else() - set( STATUS_${name} "disabled" ) - endif() -endmacro() - -#------------------------------------------------------------------------------- -# Detect in source builds -#------------------------------------------------------------------------------- -function( CheckBuildDirectory ) - - # Get Real Paths of the source and binary directories - get_filename_component( srcdir "${CMAKE_SOURCE_DIR}" REALPATH ) - get_filename_component( bindir "${CMAKE_BINARY_DIR}" REALPATH ) - - # Check for in-source builds - if( ${srcdir} STREQUAL ${bindir} ) - message( FATAL_ERROR "XRootD cannot be built in-source! " - "Please run cmake outside the " - "source directory and be sure to remove " - "CMakeCache.txt or CMakeFiles if they " - "exist in the source directory." ) - endif() - -endfunction() diff --git a/src/XrdCeph/docs/PreReleaseNotes.txt b/src/XrdCeph/docs/PreReleaseNotes.txt deleted file mode 100644 index a414e1aa765..00000000000 --- a/src/XrdCeph/docs/PreReleaseNotes.txt +++ /dev/null @@ -1,8 +0,0 @@ -====== -XRootD -====== - -Prerelease Notes -================ - - diff --git a/src/XrdCeph/docs/ReleaseNotes.txt b/src/XrdCeph/docs/ReleaseNotes.txt deleted file mode 100644 index c8a8ceb19dc..00000000000 --- a/src/XrdCeph/docs/ReleaseNotes.txt +++ /dev/null @@ -1,1692 +0,0 @@ -====== -XRootD -====== - -Release Notes -============= - - -------------- -Version 4.8.0 -------------- - -+ **New Features** - * **[XrdCl]** Local redirection and local file support. - * **[XrdCl]** merge xrdfs ls results if not unique, closes #541. - * **[XrdCl]** Provide client specific CGI info. - * **[XrdCl]** File::WriteV implementation, closes #388. - * **[XrdHttp]** Pass the HTTP verb to the external handler for path - matching. - * **[XrdHttp]** Allow one to access the XrdSecEntity object associated - with a request. - * **[XrdHttp]** Allow filtering based on HTTP verb in MatchesPath. - * **[XrdHttp]** Allow overwrites to be done on PUT. - * **[XrdHttp]** Allow multiple external handlers to be loaded by XrdHttp. - -+ **Major bug fixes** - * **[Server]** Correctly handle monEnt on file close to avoid SEGV. - Fixes #618. - * **[Server]** Poperly handle file descriptors up to 65535. - Fixes #607. - * **[Server]** Fix handling of >65K attached files (active links). - Fixes #623. - * **[Server]** Make sure doPost does not become <0 (regression introduced - in 4.7.1). - * **[Proxy]** Avoid SEGV when localroot specified w/o remote root. - Fixes #627. - * **[XrdCl]** Connection Window should be applied per IP address. - Fixes #625. - * **[XrdCl]** Write request and raw data with single writev, fixes #609. - * **[XrdHttp]** Allow XrdSfsGetDefaultFileSystem to be called multiple - times. - * **[XrdHttp]** Correct external handling logic. - * **[XrdSecgsi]** Use stack for proper cleaning of invalidated CRLs and CAs. - -+ **Minor bug fixes** - * **[Server]** Print error msg and close socket when a FD cannot. - be handled. - * **[Server]** Close additional loophole for fstream disconnect. - * **[Server]** Always unhook the statistcs object from xfr monitoring - if hooked. - * **[Server]** Ruggedize TPC to be less sensitive to protocol violations. - * **[Server]** Correct tpc directive scanning and make it more obvious. - Fixes #604. - * **[Server]** Enable url rewrites. Eliminates GSI roadblock. - * **[Server]** Do not reference a deleted object. - * **[XrdSsi]** Make sure to finalyze all requests upon disc, fixes #616. - * **[XrdHttp]** Handle properly http.secretkey. - * **[XrdCl]** various memory releated fixes. - * **[XrdPy]** Translate binary buffers into bytes objects, closes #632 - -+ **Miscellaneous** - * **[RPM]** Add python3 sub package. - * **[RPM]** Rename python sub-package, closes #614. - * **[Py]** Facilitate building python bindings with wheel. - -------------- -Version 4.7.1 -------------- - -+ **Major bug fixes** - * **[XrdSecgsi]** Fix segv in cache checking, fixes #595 - * **[XrdHttp]** Fix for the persistent connection issue. - * **[XrdHttp]** Fix FD leak when a socket error is encountered. - * **[XrdSsi]** Avoid race condition when response is posted. - * **[XrdSsi]** Avoid state conflict when request is being processed and - client asks for response. - * **[XrdCl]** Prevent segv in case user has no name. - * **[Server]** Close link on enable errors to prevent socket leaks. - -+ **Minor bug fixes** - * **[XrdLink]** Increment the IOSemaphore once for each waiting thread. - * **[XrdHttp]** Make sure that the preexisting url tokens are properly - quoted when generating a redirection. - * **[XrdCl]** Fix invalid memory reads/writes when RAII finalizes mutex - after the object has been deleted. - -+ **Miscellaneous** - * **[XrdCl]** Log last error in case redirect limit has been reached. - * **[XrdCl]** Add option to read credentials under different fsuid/fsgid. - * **[XrdCl]** Accept empty login response for protocol <= 2.8.9 - (This is only to ensure compatibility with dCache, which - due to its inaccurate implementation of XRoot protocol in - some cases returns an empty login response for protocol - version <= 2.8.9.) - * **[XrdCl]** Add envar to config Nagle algorithm. - * **[XrdSsi]** Reinitializ response object after Finished() so it can - reused. -* **[XrdHttp]** Header2cgi directive. - -------------- -Version 4.7.0 -------------- - -+ **New Features** - * **[Proxy]** Make cache I/O synchronization tunable. - * **[Proxy]** Allow caching of S3-style objects. - * **[Proxy/Posix]** Allow Name2Name to populate cache using the LFN. - * **[Posix]** Enable LITE feature in Posix preload library. - * **[Posix]** Implement serverless file caching (disk or memory). - * **[Server]** Allow storing S3-style objects in a file system. - * **[Server]** Add xrootd.fsoverload directive to handle filesystem overloads. - * **[Server]** Allow port to be specified for a supervisor. - * **[Server]** Add org and role types to AuthDB authorization. - * **[Server]** Allow definition and test of compound authorization identifiers. - * **[Server/Packaging]** Handle systemd socket inheritance. - * **[XrdApps]** Add XrdClProxyPlugin implementation. - * **[XrdCl]** Extreme copy implementation. - * **[XrdCl]** Delegate all callbacks to the thread-pool. - * **[XrdCl]** xrdfs: add recursive list, closes #421. - * **[XrdCeph]** Added support for namelib in ceph plugin . - * **[XrdFfs]** Implement xrootdfs_create. - * **[Python]** Python 3 support in C / Python interface. - * **[XrdHttp]** Make XrdHTTP able to forward HTTP requests to an external, - optional plugin (conceptually similar to CGI). - * **[Server]** XrdSsi V2 (scalable service interface) implementation. - -+ **Major bug fixes** - * **[XrdCl]** Avoid deadlock between FSH deletion and Tick() timeout. - * **[XrdCl]** Process virtual redierections in the threadpool. - * **[Xrd] Fix handling of sendfile offset argument. - -+ **Minor bug fixes** - * **[Server]** Make file locking independent of FS plugin. Fixes #533 - * **[Server]** Correct debug message interval for free space report. - * **[XrdCeph]** Fixed internal (f)stat so that it sets S_IFREG in returned mode. - * **[XrdCeph]** properly return ENOENT when file does not exist in open for read. - * **[XrdCeph]** Fixed configuration of the XrdCephOss module. - * **[XrdCeph]** Fixed some resource leak when Posix_Open fails. - * **[XrdFfs]** Remove default fuse argument "allow_other" as it is impossible - to unset. - * **[XrdFfs]** Check file descriptor before using it in xrootdfs wcache. - * **[XrdFfs]** Add more error checks when creating write cache. - * **[XrdFfs]** Avoid using literal 1024, replace with MAXROOTURLLEN. - * **[XrdFfs]** Control allow_other by env XROOTD_NOALLOWOTHER. - * **[XrdFfs]** Rewrite xrootdfs_mknod, extract low-level function. - * **[XrdCl]** Check login resp size, fixes #530 - * **[XrdCl]** Avoid FileStateHandler deadlock while forking. - * **[XrdCl]** Handle failed stateful operations without XrdCl::File lock - being locked. - * **[XrdPosix]** Use strncpy when copying checksum. - * **[RPM]** Fix init script bad exit code, fixes #536 - * **[XrdBuffer]** Decrement total buffer count when freeing buffers. - -+ **Miscellaneous** - * **[Server]** Re-enable the oss.fdlimit directive to allow POSIX preload+xrootd. - * **[Server]** Avoid thread pile-up durin slow close operations. - * **[Proxy]** Simplify delayed destruction on wait vs post. - * **[Posix]** Convert to using universal tracing facility. - * **[CI]** Add Travis CI configuration. - * **[CI]** Add .gitlab-ci.yml for gitlab CI. - * **[Packaging]** Add a sample XrdHttp config file. - * **[Packaging]** Make RPM version configurable by the user. - * **[Packaging]** Debian packaging. - * **[RPM/CMake]** Enable c++0x/c++11 by default. - * **[Crypto] Remove unused crypto code. - * **[XrdFileCache]** Add configuration parameter for flush frequency. - * **[XrdFileCache]** Alter ram limits and blocks size parameter if caching - is on the client side. - * **[XrdSut]** New XrdSutCache based on XrdOucHash. - * **[XrdSecgsi]** do not delete explicitely the CRL in Delete. - * **[XrdSut/Crypto]** Secgsi improvements: new version of XrdSutCache, - lightweith locking (PR #539). - -------------- -Version 4.6.1 -------------- - -+ **Major bug fixes** - - * **[Server/Proxy]** Avoid SEGV when close(), closedir() returns an error. - * **[cmsd]** Fix feature interaction causing improper file existence to be sent. - * **[XrdCrypto/XrdSecgsi]** Make sure the CRL is loaded for the right CA. - * **[XrdCrypto]** Support for OpenSSL 1.1 - * **[XrdSecgsi]** do not build/package libXrdSecgsiGMAPLDAP-4.so. - * **[XrdSecgsi]** Improve detection of errors when loading CRL. - * **[XrdSecgsi]** Fix for valid legacy proxy detection (PR #469) - * **[XrdSecgsi]** Absent CRLs not an error (#465) - * **[XrdSecgsi]** Fix for CA chain verification segfault (issue #463) - * **[XrdSecgsi]** Two memory leaks (PR #503) - * **[XrdCl]** Make sure there is no request/response mismatch, when - the retry logics tries to recover from an error. - * **[XrdCl/Server]** Be case insensitive when it comes to checksum names. - * **[XrdCeph]** Fix ability to read back a file written with O_RDWR flags. - * **[XrdCeph]** Disable logging of every read and write operation. A proper - debug-level logging would be needed instead. - * **[XrdCeph]** Added statistics about read/write operations in the - close log. - -+ **Minor bug fixes** - * **[XrdHttp]** Make the XrdHttpSecXtractor API backwards compatible. - * **[XrdFileCache]** Make caching proxy configuration backwards - compatible. - * **[XrdFileCache]** Fix cache v1 to cache v2 bridge after introducing - cache v2. - * **[XrdSec]** Use CommonCrypto header instead of openssl for SHA on OSX. - * **[XrdSeckrb5]** Fix memory leaks in client context and cache. - * **[Server/Logrotate]** Make sure XRootD logrotate does not interfire with - system logrotate, fixes #490 - * ** [Server]** Avoid std::ABORT should a naked logfile path be specified. - * **[XrdCl]** Make sure ForkHandler doesn't segv if PostMaster is null, - fixes #489 - * **[Packaging]** Set the working dir to /var/spool/xrootd on CC7, - fixes #365 - * **[Packaging]** On platforms where systemd is available, manage files in - /var/run with tmpfiles.d, fixes #485 - -+ **Miscellaneous** - * **[XrdPosix]** Add new minpages option to pss.cache to support large pages. - * **[XrdPosix]** Make XrdPosix.hh a public header; closes #479 - * **[XrdApps]** Remove XrdClient dependency from xrdadler32. - * **[Server]** Add XrdCksAssist functions to help handle XRootD checksums. - * **[Server/Proxy]** Move disk sync operations out of IO::ioActive() call. - * **[Server/Proxy]** Change severity IO::initLocalStat() log message. - * **[XrdFileCache]** Ease development of decision plugins. -* **[XrdFileCache]** Use ref-counts on File objects. - -------------- -Version 4.6.0 -------------- - -+ **New Features** - * **[XrdCms]** Add non-blocking sends to avoid slow links. - * **[XrdFileCache]** File caching proxy V2 (and new pss async interface). - -+ **Major bug fixes** - * **[XrdCeph]** Account for return Ceph xattr return codes. - * **[XrdCeph]** Fixed initialization of Ceph clusters when stripers are not used. - * **[XrdCrypto]** Improved determination of X509 certificate type, - including proxy version - * **[XrdHttp]** Fix memory leak in Bridge protocol (affects HTTP). - * **[XrdSecgsi]** Several improvements in the way CRLs are checked and reloaded. - * **[XrdCl]** Protect against spurious wakeups in SyncResponseHandler. - * **[XrdCl]** On read-timeout, if the stream is broken, make sure the request and - its handler are not double deleted. - -+ **Minor bug fixes** - * **[XrdCl]** Check if the file was correctly closed upon ZipArchiveReader destruction. - * **[Server]** Add limits for prepare requests. - * **[Server]** Delete buffers when the buffer manager is deleted. Fixes #414 - * **[Server]** Do not double count overlapping spaces. Fixes #425 - * **[XrdHttp]** Allow unauthenticated https clients. - * **[XrdHttp]** Make Xrdhttp secure by default (rejecting proxy cert in the absence - of a proper SecXtractor plugin) - -+ **Miscellaneous** - * **[XrdSecgsi]** Re-activate xrdgsitest - * **[RPM]** Include xrdgsitest in xrootd-client-devel package. - * **[XrdFileCache]** Add example of filecache configuration. - -------------- -Version 4.5.0 -------------- - -+ **New Features** - * **[XrdCms]** Allow specifying a different timeout for null cached entries; fixes #413 - * **[XProtocol/XrdSec/Server/XrdCl]** Implement request signing. - * **[XrdCl]** Add ZIP extracting capability to xrdcp. - * **[XrdCl]** Include the release number in client Login request cgi. - * **[XrdCl]** Add support for spaces in file names for mv operation. - -+ **Major bug fixes** - * **[XrdCrypto/Secgsi]** Fix XrdCryptosslMsgDigest::Init ; set 'sha256' as - default algorithm. - * **[XrdCl]** Use posix semaphores for fedora >= 22. Disable - omit-frame-ponter for gcc >= 4.9.3 if custom semaphores are used. - -+ **Minor bug fixes** - * **[XrdSecsss]** Fix memory leak in sss protocol. - * **[XrdNet]** Allow hostnames to begin with a digit. - * **[XrdCl]** Fix segfault in case a user cannot be mapped to a home directory. - * **[XrdCl]** Make sure a socket is always associated with a proper poller - object (not null). - * **[XrdCl]** Fix deadlock in XrdCl::PollerBuiltIn during finalize. - * **[XrdCrypto]** Do not use md5 checksum on OSX platform. - -+ **Miscellaneous** - * **[RPM]** Include xrdacctest in xrootd-server package. - * **[RPM]** Add conditional BuildRequires for ceph >= 11. - * **[RPM]** Use compat-openssl10-devel for fedora>=26. - * **[XrdCl]** Make sure the Log class can be used by any client plugin implementation. - -------------- -Version 4.4.0 -------------- - -+ **New Features** - * **[Server]** Add new [no]rpipa option to xrd.network directive. - * **[Server]** Allow objectid's to be specified in the authorization file. - * **[Server]** Add new logging plugin interface. - * **[Server]** Fixes #345 - add sid to TOD structure (ABI compliant). - * **[Server]** Implement resource selection affinity (primarily for ssi). - * **[XrdCl]** Add Metalink support (xrdcp & API). - * **[XrdCl]** Enable metalink processing on default. - * **[XrdCl]** xrdcp: use cks.type cgi tag to select the checksum type. - * **[XrdCl]** Support local metalink files. - * **[XrdCl]** Add support for GLFN redirector of last resort. - * **[XrdCeph]** Implemented pools of ceph objects. - -+ **Major bug fixes** - * **[Posix]** Remove double unlock of a mutex. - * **[Client]** Serialize security protocol manager to allow MT loads. - * **[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 - while processing an incoming/outgoing message. - * **[XrdCl]** Fix dangling pointer issue that occurs while forking. - * **[XrdCl]** Ensure DefaultEnv is finalized after last use of the object. - -+ **Minor bug fixes** - * **[Proxy]** Avoid SEGV when printing memory cache statistics. - * **[Server]** Avoid XrdNetIF static initialization issues. - * **[Server]** Honor DFS setting when forwarding operations. - * **[Server]** Make sure lockfile time is updated in deprecated runmodeold. - * **[Server]** Fixes #344 - squash path before checking for static redirect. - * **[Server]** Free Entity before replacing it from the cache (memleak). - * **[XrdCl]** xrdfs ls does not include opaque info in a listing. - * **[XrdCl]** Eliminate unnecessary write notifications. - * **[XrdCl]** Forward xrd.* parameters from the original to the redirection URL. - * **[XrdCl]** Do not preset CWD in batch mode. - * **[XrdCl]** Be complaint with file URI scheme. - * **[XrdCl]** Fix wrong query string used for opaquefile code. - * **[XrdCl]** Translate XRootD error code to errno before passing to strerror. - * **[XrdCeph]** Fixed thread safety of filedescriptors in the ceph plugin. - * **[XrdCeph]** Protected initialization of ioCtx object and striper objects - by mutex in the ceph plugin. - * **[XrdCeph]** Fixed memory corruption in asynchronous read from ceph. - * **[XrdXml]** Make sure c-string buffes are properly terminated. - * **[XtdHttp]** Don't trim printable characters. - -+ **Miscellaneous** - * **[XrdCl]** Change the way handlers and messages are matched (use maps). - * **[Apps]** Add xrdacctest to the tools set to test access control databases. - * **[Packaging/RPM]** Set max open files limit to 65k for systemd services. - -------------- -Version 4.3.0 -------------- - -+ **New Features** - * Add option to query network configuration via configured interfaces. - * **[Proxy]** Default event loops to 3 and allow it to be set via config. - * **[Server]** Let client inform redirector why it's retrying a lookup - using the triedrc CGI element. - * **[Server]** Add cms.cidtag directive to qualify the global cluster id - (solves dpm problem). - * **[Server]** Make it possible to effeciently query an external database - for file existence via the statlib plug-in (largely for DPM).` - * **[Server]** Allow declaring extra large I/O buffers (mostly for Ceph). - * **[Server]** Allow iovec based data responses (no ABI changes). - * **[Misc]** Add back trace capability to the tool set. - * **[Misc]** Add generalized XML parsing ability. - * **[Misc]** Add metalink parsing for future integration. - * **[XrdCl]** xrdcp add env var to disable recovery - * **[XrdCl]** Add support for multiple event loops. - -+ **Major bug fixes** - * **[Server]** Correct IP address matching between IPv4 and IPv6. Fixes #300. - * **[Server]** Ruggedize cmsd thread synchronization during node deletion. - * **[Server]** Delete extraneous semaphore wait to avoid deadlock (#290). - * **[Server]** Return correct response to a delayed open. - * **[Server]** Fix build of kXR_wait message in case of delayed open. - * **[XrdCl]** Detect whether client is dual stacked based on outgoing - connection in addition to DNS registration. - * **[XrdCl]** Avoid EAGAIN loop. Fixes #303. - * **[XrdCl]** Append opaque info in case we retry at a data server after - being redirected. - * **[XrdCl]** Fix: FileStateHandler::OnOpen seqfaults when both write timeout - and OpenHandler timeout at the same time. - * **[XrdCl]** Avoid SEGV when server fails after it responds waitresp. - * **[XrdCl]** Continue processing remaining files after error occurrence. - * **[XrdCl]** Fix for dangling pointer problem in deep locate, fixes #324 - * **[Misc]** Add possibility to specify disk usage parameters in .. G, T units - using XrdOuca2x::a2sz(). - * **[Python]** Fix lock inversion in python bindings. - * **[Python]** Check if python interpreter is still initialized. - -+ **Minor bug fixes** - * **[All]** Fix numerous issues with space reporting (spaceinfo, query space, - statvfs) such a double counting, scaling, and format issues. - * **[Proxy]** Do not use the ffs code path if nothing is writable. This avoids - initialization failure when the origin is a large WAN cluster. - * **[Server]** Be agnostc NTP defaults when rotating logs (fixes new RH7 defaults). - * **[Server]** Pass correct total data length in iovec to Send(). - * **[Server]** Avoid redirection loop during error recovery in a uniform cluster. - * **[Server]** Make sure N2N gets configured for the cmsd when actually needed. - * **[Server]** Properly handle an inifit NPROC limit. Fixes #288. - * **[Server]** Make sure the cluster ID is always formatted the same way. - * **[Server]** Correctly compute timeout wait. - * **[Server/Logrotate]** Make sure rotating pattern is not expanded in an if statement, fixes #302 - * **[Misc]** Include XrdXmlReader in the spec file. - * **[Misc]** Fix bug in access statistics print. - * **[Misc]** Allow conversion of decimal numbers in XrdOuca2x::a2sz() - * **[XrdCl]** xrdfs prepare has to be provided with a filename, fixes #309 - * **[XrdCl]** Make sure recursive copy is disallowed only for checksum with user provided value, fixes #304 - * **[XrdCl]** Use the same timeout value for all close operations in xrdcp with TPC enabled. - * **[XrdCeph]** Fixed race condition in multistream access to files fo CEPH - -+ **Miscellaneous** - * **[Server]** Prevent cmsd reconnect storm when things get way slow. - * **[Server]** Changes to allow for Solaris compilation. - * **[Server]** Changes to allow for OSX compilation. - * **[Server]** Detect cyclic DNS host registration when processing '+' hosts. - * **[Server]** Display manager IP addresses during '+' host resolution. - * **[Util]** Avoid compiler warning about unsafe mktemp. - * **[App]** Do not report expected errors as errors. - * **[App]** Always show any unusual node status in the display. - * **[Client/Python]** Add MANIFEST.in for python bindings. - * **[XrdFileCache]** Implement blacklisting in a FileCache decision plugin. - * **[XrdFileCache]** Make sure requested offset is reasonable. - * **[XrdFileCache]** Return -1 and set errno when bad offset is passed in. - * **[XrdFileCache]** Only generate error for negative offsets, as per posix. - * **[XrdFileCache]** Add startup protection for ReadV, too. It was already there for Read. - * **[XrdFileCache]** Fix bug in cache scanning; simplify deletion loop. - * **[XrdFileCache]** Use bytes to calculate how many files to purge, not blocks; - subtract actual size of the file, not the length of it returned by stat. - * **[XrdFileCache]** In cache purge, use stat.mtime of cinfo file if last access time can not - be determined from contents of cinfo file. - * **[XrdFileCache]** Fix argument type from int to long long (was n_blocks, is size_in_bytes now). - * **[XrdCl/XrdSys]** Use custom semaphores only for glibc<2.21. - * **[XrdCl]** Remove libevent-based poller implementaion. - * **[XrdCl]** Report reason for reselection via triedrc CGI element. - * **[XrdClient]** Changes to allow for Fedora rawhide C++11 compilation. - * **[XrdCeph]** Fixed XrdCeph compilation for C++11 enabled compilers - * **[XrdCeph/CMake]** Fix for undefined symbols (link XrdUtils). - -------------- -Version 4.2.3 -------------- - -+ **Major bug fixes** - * **[Server]** Avoid SEGV if cmsd login fails very early. - * **[Server]** Avoid SEGV when an excessively long readv vector is presented. - * **[Server]** Rationalize non-specfic locate requests. - * **[XrdCl]** Process waitresp synchronously via Ignore return to avoid SEGV. - * **[XrdCl]** Avoid memory leak when a handler returns Ignore for a taken message. - * **[XrdCl]** Fix "tried" logic by forwarding the errNo - -------------- -Version 4.2.2 -------------- - -+ **Major bug fixes** - * **[Proxy]** Protect forwarding proxy server from slow connections. This should - fix most, if not all, SEGV's that the server encountered under heavy load. - * **[Server]** Fixes #248 Prevent infinite loop when shift arg is negative. - * **[Server]** Complain when passed I/O length is negative. - * **[Server]** Avoid execution stall during node logout when the thread limit - has been reached. - * **[Server]** Make sure to capture return code for stat() to prevent random - results. - * **[XrdCl]** Make sure to get filestate lock during timeout processing to - avoid MT intereference and possible random results. - * **[XrdClient]** Restore commented out abort() when an attemp is made to index a - vector outside of its current bounds (avoids random results). - * **[Server/Proxy]** Delay deleting a file object if the close was not successful. - This avoids deleting objects that may have pending activity resulting in an - eventual SEGV. This is a bypass fix to another problem. - -+ **Minor bug fixes** - * **[Server]** Fixes #234 Properly register all components in a mkpath request. - * Correctly handle copying into a non-existent directory when automatic - path creation is enabled. - * **[XrdCl]** xrdfs correctly handles quotations (fixes the problem with ALICE token) - -+ **Miscellaneous** - * 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. - * Protect from reads exceeding file size. Fixes #249. - * Release Stream lock before invoking callbacks. Fixes #216 - * TPC: Fix deadlock in case of error in the TPC authentication - * Increase max size of write to disk queues. - * Fix bug in endswith. Fixes #260 - * XrdCeph : fixed problem with files bigger than 2GB for synchronous writes - * **[XrdCl]** Change message loglevel from Error to Debug. Fixes #246. - * **[XrdCl]** Fix race condition in PostMaster initialization - * **[XrdCl]** Provide atomicity for PostMaster value using built-in functions - * **[XrdFileCache]** fixed deadlock on immediate file close (e.g. xrdcp to non-writable output) - * **[XrdFileCache]** fixed errors on some posix operations using virtual mount - -------------- -Version 4.2.1 -------------- - -+ **Miscellaneous** - * **[Client/Cl]** Make sure kXR_mkpath is set for classic copy jobs when the - destination is xrootd (backward compatibility fix). - -------------- -Version 4.2.0 -------------- - -+ **New Features** - * **[Client/Python]** Integrate xrootd-python into the main package. - * **[Server]** Include a Ceph OSS plug-ing. - * **[Server]** Implement throttling. - * **[Server]** Detect redirect loops using "tried" token. - * **[Server]** Implement the "cid" option for config query to display the - unique cluster ID. - * **[Server]** Allow suspending and enabling remote debugging without a - restart. - * **[Server]** Implement black/whitelist with optional redirection. - * **[Server/Proxy]** Add the xrdpfc_print tool to print the caching - proxy metadata. - * **[Server/PlugIns]** Provide a mechanism to pass command line arguments - to plug-ins. - * **[Server/PlugIns]** Provide access to the native and the active extended - attribute implementation. -+ **Major bug fixes** - * **[All]** Fix various memory access issues. - * **[Server]** Fix various IPv4/IPv6 compatibility issues. - * **[Server]** Avoid disabling of frm notifications due to plug-in - initialization issues. - * **[Server/Proxy]** Avoid holding a global lock when opening/closing files - to solve timeout issues. - * **[Security/GSI]** Fix reloading of CA and CRLs. -+ **Minor bug fixrs** - * **[Server/HTTP]** Fix issues related to invalid chunk sizes. - * **[Server/Proxy]** Various logic and permission processing fixes. -+ **Miscellaneous** - * **[Client/Cl]** Make the compiler issue warnings when the return codes - from the File and FileSystem methods are unchecked. (issue #188) - * **[RPM]** Disable building of the compat package by default. - * **[Server/Proxy]** Avoid serializing stat() via the proxy to improve - performance. - * **[Tests]** Factor out the common testing code from the client tests so - that it can be re-used. - -------------- -Version 4.1.2 -------------- - -+ **Major bug fixes** - * **[Utils]** Don't confuse -I and --tpc while parsing commandline parameters - for xrdcp. (issue #213) - * **[Server]** Fix various IPv4/IPv6 issues. (issues #164, #227) -+ **Minor bug fixes** - * **[Client/Cl]** Print mtime when doing xrdfs stat. - * **[All]** Fix some memory access issues. (issues #186, #197, #205) - * **[Server]** Recreate logfile fifo if it already exists and is a file. - (issue #183) - * **[Server]** Properly reset suspend state when reconnecting cmsd. - (issue #218) - * **[Server]** Avoid disabling async I/O when using an oss plugin that does - not implement file compression. (issue #219) - * **[Server]** Do not debit space when relocating a file within the same - partition. - * **[Server]** Fix meta-manager port directive ordering. - * **[Server/Logrotate]** Do not print anything to stdout to avoid making cron - send emails to admins. (issue #221) -+ **Miscellaneous** - * **[Server/Proxy]** Disable POSC processing when a proxy plugin is loaded. - -------------- -Version 4.1.1 -------------- - -+ **Major bug fixes** - * **[RPM]** Remove the library patch from xrootd-config to enable multiarch - installations. - * **[RPM]** Move the user creation scriptlets to xrootd-server where they - belong. (issue #179) - * **[Server]** Fix PowerPC compilation. (issue #177) - * **[Server]** Avoid the pitfalls of infinite nproc hard limit in Linux. - * **[Server]** Correct flag definition to include cms plugin loading. (issue #176) -+ **Miscellaneous** - * **[Man]** Update documentation. - * **[Client/Cl]** Set the multi-protocol ability basing on an environment - variable. - -------------- -Version 4.1.0 -------------- - -+ **New Features** - * **[Everyting]** Implement dynamic plugin shared library filename versioning - to allow multiple major versions to co-exist. - * **[Server]** Compelete IPv6/IPv6 and public/private network routing. - * **[Server]** Allow the checksum manager to use OSS layer to access data. - (issue #140) - * **[Server]** Allow the definition of subordinate clusters. - * **[Server]** Support multiple checksum types. Client can select non-default - checksum using the "cks.type=" cgi element. - * **[Server]** Provide plugin interface for handling extended attributes. - * **[Server]** Add options to xrd.network to control keepalive. - * **[Server]** Control core file generation via xrd.sched core directive. - * **[Server]** Add pss.permit directive to restrict outbound connections for - forwarding proxies. - * **[Server]** Allow xrootd to handle objectid names as exports. - * **[Server]** Install and package the cluster mapping utility: xrdmapc. - * **[Server]** Allow the specification of xrootd.seclib default. - * **[Server]** Pass along XRD_MONINFO setting and application name to - monitoring. - * **[Server/Proxy]** Implement a forwarding proxy option. - * **[Server/Proxy]** New configuration of XrdFileCache using 'pfc.' prefix. - * **[Sever/HTTP]** Support gridmap parsing. - * **[Client/Cl]** Inform the server about availability of local IP address - types (IPv6/IPv4, public/private) to in order to facilitate redirections. - * **[Client/Cl]** Make the client send kXR_endsess request when recovering - broken connection - avoids 'file already open' errors. - * **[Client/Cl]** Implement TCP keep-alive support. - * **[Client/Cl/xrdcp]** Optimize xrdcp uploads by compensating for latency. - * **[Client/Cl/xrdcp]** Make it possible for xrdcp to run multiple transfers - in parallel using the '--parallel' option. - * **[Client/Cl/xrdcp]** Make it possible for xrdcp to concatenate multiple - sources to stdout. - * **[Client/Cl/xrdfs]** Add xrdfs locate -i option to ignore network - dependencies (IPv6/IPv4). - * **[Security]** Add new security framework loader to allow external pacakges - that linked against security plugins to dynamically load them instead. - * **[Security/sss]** Allow forwardable sss tokens when ecrypted with a - forwarding key as defined by the xrdsssadmin command. - * **[Plugins]** Implement generic matching rules to version check 3rd party - plug-ins. - * **[Packaging/RPM]** Add SystemD configuration files for RHEL7. - * **[Packaging/RPM]** Introduce compat RPM packaging providing xrootd 3.3.6 - deamons and libraries with the ability to switch between desired versions - using the sysconfig file. - * **[Packaging/RPM]** The RPM naming has been switched back to xrootd - (from xrootd4). - * **[Utils]** Add xrootd-config utility. - -+ **Major bug fixes** - * **[Server/HTTP]** Make it possible to handle files larger than 2GB. - * **[Server]** Prevent blacklisting of all connctions when role is supervisor. - * **[Server]** Fix bug in handling cms.dfs redirect verify that would keep - the client is an infinite wait loop. This also affected locate requests - regardless of what the redirect option was set to. - * **[Server/Proxy]** Avoid SEGV when no environment has been passed in the - proxy server. - -+ **Minor bug fixes** - * **[C++ API]** Provide complete portability and correct behaviour across - platforms with and without Atomics. This patch does not change any ABI's. - * **[Server]** Do not set *TCP_NODELAY* for unix domain sockets as this - issues a nasty error message. - * **[Server]** Allow cms.dfs mdhold argument to be 0 as documented. - * **[Server/Plugins]** Add missing initializer to the LocInfo structure. - * **[Server/Plugins]** Correct header define gaurd in XrdSfsFlags.hh. - * **[Server/Proxy]** Fully support extended file system features and pass - those features through a proxy server. (issue #115) - * **[Client/Cl]** Remove duplicates from the HostList. - * **[Client/Cl]** Fix minor atomicity issues (C++11). - -+ **Miscellaneous** - * **[Server]** Actually remove xmi plugin handling as xmilib is no longer - supported. - * **[Server]** Make sure to always passhrough CGI information. - * **[Server]** Honor network routing when creating the client's i/f - selection mask. - * **[Server]** Efficiently handle replicated subscribers (i.e. managers). - * **[Server/HTTP]** Remove useless loading the security framework. - * **[Server/Security]** Add new NetSecurity::Authorize() method that accepts - text. - * **[Server/Proxy]** Properly support proxying objectids. - * **[Server/Proxy]** Clean-ups in the caching proxy. - -------------- -Version 4.0.4 -------------- - -* **Major bug fixes** - * **[Client/Cl]** Properly allocate buffers for error messages. (issue #136) - * **[Client/Cl]** Check if there is enough data before unmarshalling. - * **[Client/Cl]** Fix a memory leak in MessageUtils::WaitForResponse - affecting all synchronous calls. - * **[Client/Cl]** Prevent a segfault in the destructor when called after - the libXrdCl library has been finalized by the linker - ROOT garbage - collection. https://github.com/cms-externals/xrootd/pull/1 - * **[Client/Posix]** Fix broken readdir_r() and readdir_r64() functions. - * **[Server]** Use correct flag when adding a cluster. The bug made it - impossible to have more than one supervisor node. - * **[Server/Logrotate]** Prevent stack corruption by correctly sizing the - timestamp buffer. - -+ **Minor bug fixes** - * **[Client/Cl]** Properly check if a recursive copy was requested to avoid - unnecessarily stating the source. - * **[Client/Cl]** Avoid inserting duplicate entries to HostList when retrying - at the same server. - * **[Client/Cl]** Normalize (trim leading zeroes) before comparing adler and - crc checksums. (issue #139) - * **[Client/Posix]** Prevent mkdir failure in a clustered environment by - creating the full directory path by default. - * **[Client/Possix]** Fix a memory leak when doing deep locate. - * **[Server/Logrotate]** Use expect to send a ping to pipes. This prevents - logrotate from hanging when nobody is listening at the other end of the - pipe. - * **[Authentication/Client]** Pass the external environment to the protocol - manager. (issue #133) - * **[Authentication/sss]** Fix a memory leak. - * **[Utils]** Avoid SEGV when assigning a unix domain address to a - NetAddrInfo object previously used to hold a TCP domain address. - * **[Server/cmsd]** Use the same write selection rules for dfs and non-dfs - environments. - -+ **Miscellaneous** - * **[Server/Logrotate]** Prevent the default configuration from sending - emails to admins and from creating a new log after the old one has - been rotated. (issue #135) - * **[Server/SELinux]** Using expect in logrotate requires the logrotate_t - context to have access to pseudoterminals and tmpfs as well as stating - fifos - * **[Client/Commandline Parser]** Allow local to local copy in new xrdcp but - not in the old one. - * **[Client/Cl]** Discard a whole cluster on failure in federation context. - (issue #132) - -------------- -Version 4.0.3 -------------- - -+ **Major bug fixes** - * **[Server]** Make sure the network routing is honored in all cases. This - fixes problems encountered by sites whose clients use a private IP address - to connect to a redirector's public IP address. (issue #130) - -------------- -Version 4.0.2 -------------- - -+ **Minor bug fixes** - * **[Client/Cl]** Handle all non-NULL-terminated error responses correctly. - * **[Client/Cl]** Release old auth buffer when reconnecting after TTL - expiration. - -+ **Miscellaneous** - * **[Client/Cl]** Retry after an incomplete local write. This produces - clearer error messages. Ie: "Run: [ERROR] OS Error: No space left on - device" instead of: "Run: [ERROR] OS Error: Operation now in progress". - * **[Client/Cl]** Don't force a server to issue a short read when fetching - last data chunk. This works around issues for proxied FAX sites. - -------------- -Version 4.0.1 -------------- - -+ **Major bug fixes** - * **[Server]** Prohibit accessing memory via /proc using digFS. - -+ **Minor bug fixes** - * **[Server]** Prevent over-scan of the xrd.network routes option which may cause - a config file error message and initialization failure. - * **[Server]** Fixes to make things compile on ix86, arm and ppc64. - * **[Server]** Correct protocol name supplied to monitoring for userid. - * **[Server/Proxy]** Various minor fixes to caching proxy. - * **[Security]** Check the length before looking inside a SUT buffer. (issue #126) - * **[Client/Cl]** Check for copy source and target validity to display proper error - messages. - * **[Client/Cl]** Return default plug-in factory for an empty URL. (issue #120) - * **[Client/Posix]** Provide full error mapping for POSIX interface. - * **[All]** Remove some unnecessary commas and semicolons. (issue #121) - -+ **Miscellaneous** - * **[Server]** Pass client login information to monitoring. - * **[Client/Cl]** Make xrdfs locate -h synonymous to locate -m. - * **[Client/Cl]** Add -i option to xrdfs locate setting the Force flag. - * **[Docs]** Various documentation updates. - -------------- -Version 4.0.0 -------------- - -+ **New Features** - * Supprt IPv6. Please read docs/README_IPV4_To_IPV6 for details. - * Introduce the XrdFileCache library - a proxy server plugin used for caching - of data into local files. - * Beta support HTTP(S). - * Provide protocol bridge to let other protocols use xrootd back-end plugins. - * Provide full support for public/private IP networks. - * Allow remote debugging via the xrootd.diglib directive. - * Provide a mechanism to manually control log file rotation via -k and add - support for logrotate. - * Add -z option to enable high recision log file timestamps. - * Define a new plug-in to allow replacement of the stat() function when - used to determine exported file characteristics. This plug-in is meant - to be used by tape-backed file systems that identify offline files in - odd ways (e.g. GPFS). Patch assumes XRDROLE patch below. - * Implement full readv-passthru for enhanced performance. - * Add a disconnect record to the f-stream. - * xrdcp is now the same as xrdcopy, and old xrdcp is now xrdcp-old - * Make clients configurable via /etc/xrootd/client.conf and - ~/.xrootd/client.conf - * Implement a plug-in system for client's File and FileSystem queries. - * Make it possible for 'xrdfs stat' to query for combination of flags. - * Make third party copies cancellable. - * Implement xrdfs spaceinfo, cat and tail commands - * Terminate iddle connections after a timeout and treat timeouts on streams - that should be active (because of outstanding requests with no delay times) - as errors. - * Implement XrdCl::File::Visa and XrdCl::File::Fcntl. - * Support for full URL redirects. - * File and Filesystem objects implement property system to pass custom - information to and from them (including plug-ins) without breaking - ABI. - * Add --dynamic-src to xrdcp options to allow dynamic file copying. - * Implement the directory listing in bulk. - * Enable locate to return host names not just IP addreses. - * Implement node blacklisting for the cmsd (see cms.blacklist directive). - * Add mv command to frm_admin. - * Allow query of current role and dynamic cms state via kXR_query. - * Implement query config chksum to return supported chksum name. - * Add version as a variable that can be returned by kXR_Qconfig. - * Add sitename as an argument to kXR_Query+kXR_Qconfig. - * Provide disconnect notifiation to underlying file system. - * Provide the filesystem plugin a way of creating a session storage area. - * Add flag to indicates a secondary copy of a file exists. - * Allow testing for undefined set/env vars via if-else-fi. - * Add '-L' flag to the xrootd command to allow loading a protocol library - * Add flag to indicates a secondary copy of a file exists - - -+ **Bug fixes** - * Fix various dead locks in the IOEvents poller. - * Implement LinuxSemaphore class in order to replace buggy POSIX semaphores - on Linux. - * Honor the cmsd.dfs directive for locate request to avoid placing a - file in ENOENT status. - * Make sure that the old client runs only in IPv4 mode as mixing modes does - not work for a variety of reasons. - * Accept old-style as well as new-style IPv6 addresses in the sss - protocol. This allows the new client to use this protocol after - it implemented IPv6 support. - * Prevent invalid mutex operations in auto-termination routine. - * Resolve naming conflicts within the frm that resulted from the - statlib plugin implementation. - * Do not rely in file locking to serialize inter-thread access. This - fixes the prolem of usage file drift. - * Fix various parse context issues in copy config with --recursive. - * Recognize object deletion in the error handling path. - * Use atomic FD_CLOEXEC where available to prevent FD leaks. - * Squelch casting complaints from C++11. - * Make sure to return all nodes in a star locate request. - * Always load protocols in the specified order. - * Fix xrootdfs wcache crashing issue when using virtual file descriptor. - * Fix selection of a server when a DNS entry resolves to more than one. - * Correct pthread_cond_timedwait() time calculation and error handling. - * Fix null insertion of hostname in error message when open fails. - * Fix issues with extensions in GSI proxies - * Fix problem with creation of the forwarded KRB5 ticket - * Correctly handle reading of a partial readv headers (issue #45) - * Make sure to propagate username and password when redirecting - * Honor request timeouts when processing kXR_wait - -+ **Miscellaneous** - * XrdClient and associated commandline utilities are now obsoleted. - * Propagate info about partial success from deeplocate to dirlist. - * Remove perl interface. - * Send timezone, country code and application name while logging in. - * Change interfaces to copy process to use property system (allows for - adding features without breaking the ABI). - * Final change to f-stream monitoring. Replace standard deviation - (sdv) calc with reporting sum of squares (ssq) counts. - * Make public headers compile cleanly with -Wall -Wextra -Werror. - * Support passing cert, key paths via URLs - * Allow testing of undefined set/env vars via if-else-fi - * Pass user environment settings settings in the login CGI - * Use DNS names instead of addresses for kXR_locate when listing - -------------- -Version 3.3.6 -------------- - -+ **Minor bug fixes** - * Prevent SEGV when error occurs during stat (issue #70) - * Prevent SEGV in redirect monitoring (issue #61) - * Set reasonable linux thread limit and warn it we cannot do so. - -+ **Miscellaneous** - * Support for C++11 (narrowing fixes, unique_ptr vs. auto_ptr) - * Support for CMake 2.8.12 (interface link libraries) - -------------- -Version 3.3.5 -------------- - -+ **Minor bug fixes** - * Fix minor Coverity issues in XrdCl - * Fix a rarely occuring segfault when forking XrdCl under heavy load - * Fix various issues related to group name retrieval (issues #51, #52, #53) - -+ **Miscellaneous** - * Make XrdSys/XrdSysIOEvents.hh private - could not have been used anyways - * Add a sysconfig template to preload custom allocators in order to fix - memory issues on RHEL6 - * Allow up to 63 characters for a site name - -------------- -Version 3.3.4 -------------- - -+ **Major bug fixes** - * Serialize sss authentication client initialization to prevent race - conditions - * Actually cancel the JobManager threads while stopping it - this affected - client side fork handling (new client) - * Restore original meaning of -adler and -md5 to xrdcp (issue #44) - -+ **Minor bug fixes** - * Append CGI info when retrying at a server that handshaked but never - respnded to the request (xrdcp) - * Do socket accepts asynchronously to prevent DNS resolution from blocking - accepts (issue #33) - * Warn about incomplete dirlist responses (xrdfs) - * Cast the utilization statistics to uint16_t before printing to - print actual numbers instead of letters corresponding to ASCII codes - (xrdfs) - -+ **Miscellaneous** - * When calling File::Stat use file handle instead of path - * Improve handling of malformed kXR_readv responses (new client) - * Explain parameters of xrdcopy --tpc (documentation, issue #46) - -------------- -Version 3.3.3 -------------- - -+ **Major bug fixes** - * Prevent SEGV's when reusing a recycled protocol object under certain - conditions (xrootd server) - * Prevent SEGV when using the -DS/-DI commandline parameters in xrdcp - (issue #13) - * Prevent integer overflow when calculating client recovery windows - * Make sure the new client tries all available authentication protocols - when connecting to a security enabled server (issue #14) - * Detect buffer size mis-matches when server returned valid response with - invalid size (xrdcopy) - * Recognize /dev/null and /dev/zero as special files when using copy - commands - -+ **Minor bug fixes** - * Prevent the new client deadlock on Solaris and MacOS when using - the built-in poller and connecting to localhost (issue #5) - * Compensate for ROOT garbage colletion issues when calling the - new client code - * Avoid favoring socket writes when using new client with the built-in - poller - * Strip off opaque information from dest filename when copying to local - filesystem using xrdcp (issue #21) - * Fix setting client timeout resolution while connecting to a server - -+ **Miscellaneous** - * Change the RPM package layout to match the one used by EPEL (issue #12) - * Drop the daemon user RPMs - * Allow new client connection parameters to be tweaked by connection URL CGI - * Make the built-in poller default again in the new client - after resolving - issue #5 - -------------- -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 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 - changes some low level public interfaces, so the soname of - libXrdCl.so has been bumped to libXrdCl.so.1. The xrootd.org - RPMs also provide the old libXrdCl.so.0 in order to preserve the - binary compatibility with the clients linked against it. - -------------- -Version 3.3.1 -------------- -+ **Major bug fixes** - * Correct XrdClient ABI incompatibility issue introduced in 3.3.0 - * Install additional private headers - -------------- -Version 3.3.0 -------------- -+ **New Features** - * Stable interfaces immutable in minor releases (except XrdCl). Only - public header files are installed in the usual include directory. - In order to ease up transition of some clients some of the private - include files are also installed in private subdirectory. - * New asynchronous and thread-safe client libraries and executables - (XrdCl). The ABI compatibility is not guaranteed until 4.0.0. - * Build the xrootd protocol plugin as a shared library. - * Add the altds directive to allow pairing a cmsd with an alternate data - server. - * Differentiate between packed and unpacked readv monitoring records. - * Allow plugin libraries to be preloaded. This feature is only meant - for MacOS. - * Include optional site name in summary monitoring records. - * Include optional site name in server identification record if the - site name was specified on the command line (-S) or via config - file (all.sitename directive). - * Define a standard supported mechanism to obtain the default storage - system object. - * Provide an ABI-compatible interface to obtain a default cmsd client - object. This patch does not change the definition of the XrdCmsClient - object and is ABI compatible with all previous releases (DPM support). - * Allow multiple comma separated protocols in XrdSecPROTOCOL client-side - envar. This allows the client to select 1 of n protocols. - * Implement new "f" stream monitoring. - * Add new summary counters for readv and readv segs. - * Add boiler plate comments indicating the all software is licensed under - LGPL. No functional source code was modified by this patch. - * Add GPL and LGPL license text. - * Liberlize locking structure to prevent lock inversion relative to - external locks. - * Provide libevent replacement for Linux (epoll), Solaris (poll_create), - and others (poll). Note: versions of Solaris less than 10 are no longer - supported and they will no longer compile with this update! - * Provide a libevent type replacement package. - * Allow tracker files (e.g. ".fail") to be placed in a shadow directory. - This is controlled by the new fdir option on the oss.xfr directive. - * Allow meta-files (i.e. .fail file) to be relocated to a shadow directory - using the oss.xfr directive. This avoids polluting the exported name - space when an frm transfer operation fails. - * Create a general place for platform dependent utility methods. - * Add third party copy statistics to the summary record. - * zlib compatible checksum plugin - -+ **Major bug fixes** - * Serialize access to cache entries to prevent SEGV's. - * Fix the fast response queue so that it doesn't run out of response - slots causing a big performance penalty. This is a high priority fix. - * Properly disarm the mutex helper when the mustex object is deleted. - * Use correct variable to hold osslib parameters. This patch fixes commit - 2e27f87a (version checking) and without this patch makes it impossible - to load an oss plug-in. - * Properly check for errors when client read returns 0 and reflect true - status. This only affects the Posix client interface. - * Remove redundant flag indicating a running poller. This may cause the - poller to never be woken up when a timeout value changes. - * Fix tag in ofs statistics. It is improperly terminated and may - cause certain xml parsers to fail; rendering monitoring useless. - * Undo the side-effect of commit ff8bdbd6 that prevented the frm from - sending stage notifications to xrootd; causing opens and xrdstagetool - to hang with dynamic staging enabled. - * Make sure the id buffer is large enough to hold all id combinations. - * Avoid deadlock when closing a Posix File with an active preread. - * For concurrent queries for the same file allow servers to respond to the - query and only redirect clients to a stageable server if the file is not found. - -+ **Minor bug fixes** - * Add EPOLLRDHUP to avoid leaving sockets in CLOSE_WAIT with a one-shot - poll framework. - * Fully integrate checksum processing into a manager node. When configured, - it does not matter whether a client directs a checksum request to a manager - or a server. This also fixes bug report #93388. - * Make sure to reflect proper range of errors during read/write operations. - This also provides filesystem plugins full range of allowed return codes. - * Initialize the rMon toggle to avoid valgrind complaint. - * Fix minor issues reported by Coverity. - * Make sure opendir() returns a null pointer when the directory doesn't - exist. - * Make sure that XrootdFS returns ENOENT when opendir() returns a null. - * Make sure to use correct time to set mtime/atime after a physical reloc. - * Prevent hangs when doing exterme copy from server to server. - * Fix the -force option to really work for the mark subcommand. - * Pass through error code returned by the N2N plug-in. This only affects - the proxy server and caused feature interference. - * Automatically exclude originating server/cluster on an enoent static - redirect. - * Correct typos XRDPSOIX envars should really be named XRDPOSIX. - -+ **Miscellaneous** - * Remove superfluous includes or other move includes to eliminate - unnecessary dependencies in ".hh" files. This patch is required - to create an EPEL conformable include directory. - * Add port to prepare request struct as documented in 2.9.9. - * Add pathid to readv request struct as documented in 2.9.9. - -------------- -Version 3.2.6 -------------- -+ **Major bug fixes** - * GSI authentication: fix possible race condition while re-loading CA - certificates; fix also related memory leaks. - * GSI authentication: make sure the CA cache is not initialized twice (e.g. - server and client inside there), and that the cache entry pointers are - always initialized. - * Crypto OpenSSL modules: use more appropriate way to read the RSA complete key, - solving various issues for RH6 and derivations, included SL(C)6. - * Make sure redirect opaque information is passed along for all filename - based requests. This is required for DPM and EOS N2N services to work - in all cases (most importantly, stat). - * Make sure buffer ends with null byte before read suspension. This only - occurs on very heavily loaded connections. - * Fix the fast response queue so that it doesn't run out of response - slots causing a big performance penalty. This is a high priority fix. - -+ **Minor bug fixes** - * Properly detect external process failure and report correct error status - to a client. This also fixes bug report #91141. - * [XRootDPosix] Make sure to use a supplied cache even when no cache - directives given. - * Make sure to return a usable path string via XrdOucCacheIO::Path(). - * Actually support 4 different redirect destinations. - -+ **Miscellaneous** - * Transparent support for new name hashing algorithm adopted in openssl - 1.0.0x (GSI authentication protocol) - * Verbosity levels revised for GSI and PWD authentication protocols. - * Notification of initialization option for GSI and PWD authentication - protocols. - * Do not repudiate file existence on an "cancelled" error during open. - this patch addresses overloaded dCache pool nodes. - -------------- -Version 3.2.5 -------------- -+ **Major bug fixes** - * Make realoading gridmapfile atomic (protect from segfault) - * Propagate to clients proper range of errors during read/write operations - * Fix segfault when handling writes to files that have not been opened - -------------- -Version 3.2.4 -------------- -+ **Major bug fixes** - * Work around a dead-lock in the client fork handlers. - -------------- -Version 3.2.3 -------------- -+ **Major bug fixes** - * Make sure read statistics are updated for sendfile() and mmap I/O. - * Make sure refresh thread is dead before deleting deleting the keytab to - avoid SEGV's. - * Add missing include for compiling with gcc-4.7 (from Sebastien Binet). - This patch is required for successful compilation. - * Avoid segfaults when limiting number of redirections caused by failed - authorization. - * Avoid deadlock in the client fork handlers. - -+ **Minor bug fixes** - * Correct monitor initialization test to start monitor under all configs. - * Fix a memory leak in the client handshake algorithm. - -+ **Miscellaneous** - * Make RHEL6-created SRPMs buildable on RHEL5 by forcing RPM to use MD5 - digests. - * Fuse: Use default TTL values for data server connection and load - balance server connection. - -------------- -Version 3.2.2 -------------- -+ **Major bug fixes** - * Correct test whether or not to initialize redirect monitoring. The old - code never initialized it this disabling redirect monitoring. - * Backport frm notification fix that stalled stage-in requests from commit - 69e38cfd6b8bb024dd34f8eb28a666fbf97f346b - * Prevent SEGV when xrd.monitor rbuff value not specified - * Prevent xrdcp hangs when doing exterme copy from server to server. - * In case of 'limited proxy' look for VOMS attributes also in the parent - proxy. - * Correct log processing for sites that use the root directory as the - stomping ground for newly created files. - -------------- -Version 3.2.1 -------------- -+ **Major bug fixes** - * Don't build sendfile support on MacOSX because it doesn't work - * Prevent double-free abort when more than 16 files have been opened by a - client and the client terminates the session without closing the 17th one. - -------------- -Version 3.2.0 -------------- -+ **New Features** - * Retool the XrdOucCache object so that cache implementations can be - implemented as plugins. - * Add FSize method to the XrdOucCacheIO object to ease implementation - of disk caches containing partial files. - * Add the pss.cachelib directive to specify a cache plugin. - * Implement ultralow overhead redirect monitoring. - WARNING: ofs plugin writers will need to recompile their plugin interface - to be fully compatible with this commit due to additional - information passed to the ofs object "new" methods. - * Allow the XrdCmsClient interface (a.k.a Finder) to be a plug-in. - * Add ofs.cmslib directive to specify the XrdCmsClient plug-in. - * Add new class, XrdOucCallBack, to simplify using callbacks in the - XrdCmsClient plug-in. - * Define the frm.all.monitor directive to enable migration, purging, and - staging monitoring. This was originally part of xrootd.monitor but that - just was odd. Note that the stage, purge, migr events are no longer - accepted on the xrootd.monitor directive. - * Collapse he staging (s) and migration (m) records into a single transfer - (x) record. While not compatible, the previous implementation was new - code and no one actually was capturing these records. - * Implement a server identification record (=) that unquely identifies each - server. The record can be sent periodically and can be used as a heartbeat. - * Add -y option to xrdcp to limit number of extreme copy sources. - * Uniformly pass the execution environment to all oss and cms client - methods. This is largely for DPM support. - WARNING: While this update is binary backwad compatible to existing oss - plug-ins it is not source compatible. Plug-in writers will need - to modify their oss methods to successfully compile. - * Allow an automatic redirect when a file operation ends with ENOENT. - Allow redirects for chsum and trunc operations. - Both of the above are controlled via the xrootd.redirect directive. - * Report the timezone when connecting to a [meta]manager. - * Allow configuration of staging, migration, and purging events. - * Allow transfer script to inject information into the monitoring stream. - * Report number of attempted login, authentication failures, successful - authenticated and unauthenticated logins in the summary statistics. - * Indicate whether a disconnect was forced and whether it was a parallel - path (as opposed to a control path) in the monitoring record. - -+ **Major bug fixes** - * 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. - This eventually causes a SEGV when detailed monitoring is enabled. - * Remove improper initialization that may cause a SEGV in the checksum - manager. - * Add missing initializer without which we will get a SEGV. This is a fix - for the just added monitoring code. - * Remove regressions that prevent a proxy cluster from being fully - configured. - -+ **Minor bug fixes** - * Correct debug message frequency that caused people to think some file - system partitions were being ignored. - * Correct pthread Num() to return thread-specific numbers. - * Make sure the sendfile interrupt counter is initialized to zero. - * Make sure to honor absolute cms.space values when percentage not - specified. - * Prevent double user map record when monitoring when auth is configured - but not actually monitored. - * Take timezone changes into account when waiting for midnight. This solves - the log rolling problem when changing between DST and standard time. - * Make sure to cut close records for open files during a forced disconnect - when monitoring file information. - * Do not create meta-files or update extended attributes when placing a - file into read-only space. - -+ **Miscellaneous** - * Bonjour code dropped - * Complete implementation of the fstat() version of stat(). - * Consistently pass the enviroment to the cms client enterface. - * Make return codes consistent between synchronous & async XrdCmsClient - returns. - * Document the XrdCmsClient interface in the header file. - * Cut close monitor records before cutting the disconnect record. - * Make frm_purged and frm_xfrd use sparate log files. - -------------- -Version 3.1.1 -------------- - -+ **New Features** - * Compile on Solaris 11 - * Add support for sending DN with monitoring information - * Add possibility to switch off automatic download of CRL from the web; - default is OFF; to enable it multiply by 10 the relevant CRL options - (i.e. 12 and 13 are like 2 and 3 but trying download if the file is not - found). - * Add refresh frequency time for CRL's; default 1 day . - -+ **Major bug fixes** - * Fix various client threading issues. - * [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 - xrootd until now have disabled use of sendfile() with resulting poor - performance. This fix corrects this. - * Don't force libXrdPss.so to be loaded for proxy managers. - * Fix various CMake issues: disable library inheritance, fix underlinking - problems, make sure libcom_err is present when building kerberos. - * Replace non-reentrant versions of getpwxxx and getgrxxx with reentrant - versions. This should prevent spurious uid/gid translations. - * Fix RedHat bug #673069: Missing header files required by DPM - * Don't ignore errors returned by kXR_close - * Init scripts: don't change the ownership of the sysconfig files - preventing the xrootd user from executing arbitrary code as root - -+ **Minor bug fixes** - * Add 'k' to the option list. It was wrongly deleted in the last option - refalgamization. - * Fix a typo in the specfile causing problems with multithreaded - compilation. - * Initialize xattr variable name so that xrdadler32 can fetch previous - checksum. The error caused xrdadler32 to always recompute the checksum. - * Make sure that monitor write length is really negative. - * Add the oss.asize hint to the destination URL in all possible cases. - * Properly print adler32 checksum in xrdcp. - * When the server certificate is expired, try to renew from the same path - before failing. - * Get the signing certificate for the CRL from its issuer hash, which can be - different from the CA hash. - * Add check for the format of the downloaded CRLs: DER or PEM - * Solaris init script: switch to xrootd user when invoked as root - * RHEL init scripts: always create /var/run/xrootd to handle /var/run - being mounted as tmpfs - -+ **Miscellaneous** - * Relax requirements on the permission mode of the x509 key files - * Disable client redirections reports to the console. - * Stop doing XrdFfsPosix_statall() if task queue is long. - * Get rid of compiler warnings - * Improve some log messages - * At server startup, only initialize the CA (and CRL, if required) for the - authority issuing the server certificate; additional CA's are initialized - only if needed. - -------------- -Version 3.1.0 -------------- - -+ **New Features** - * Use CMake to build the source code and retire all the other build systems. - * Add IOV as a selectable detail to xrootd.monitor directive. - * Provide a mode in xrootdfs to auto-update internal list of data servers. - and extend client connection TTL from one hour to infinity. - * Provide virtual xattr ("xroot.cksum") to obtain checksum for consistency. - * Make xrdadler32 use the new checksum format if it is set (fallback to old - format otherwise). In all cases, the old format is converted to the new - format whenever possible. - * Enforce r/o exports in the proxy server (finally added). - * Allow auto-fluching of I/O stream monitoring (default is off). - Patch submitted by Matevz Tadel, UCSD. - * Make proxy honor the export list at the storage layer. This allows sites - to disable staging via the proxy by specifying nostage for otherwise locally - stageable paths. - * Do not export the stage attribute to the meta-manager unless the path is - tagged with the stage+ attrbute on the export directive. - * WARNING: This update makes the oss plug-in source incompatible because an - additional parameter was added to the Stat() method. The update is binary - compatible and so only affects sites that recompile their plug-in. - * Allow the query checksum request to be issued via a proxy server. - * Add a query checksum interface to the POSIX interface. - * Defines the livXrdSecgsiAuthzVO plug-in to allow easy mapping from voms - vo names to users and groups. The plugin is configurable at run-time. - * Allow the OucErrInfo object to point to an environment. - * Add method to SysDNS to format an AF_INETx address into the RFC IPV6 - recommended format. - * Allow pointers to be placed in the OucEnv environment table. - * Extend the kXR_protocol request to allow the server to return detailed - information about node's role. This is backwardly compatible. - * The client uses kXR_protocol request to query for the server's role - (to distinguish managers from meta managers). - * The client goes back to a meta manager on authentication failure. - * The client prints to stdout the redirections it gets. This behavior may be - disabled by setting the XRD_PRINTREDIRECTS environment variable to 0, or, - from C++ by saying: EnvPutInt( NAME_PRINT_REDIRECTS, 0 ) - * Set $HOST value for possible copycmd substitution. - * Phase 1 to allow for redirection monitoring. Add rbuff and redir options - to the xrootd.monitor directive. - * Add error, redirect, and delay counts to the xrootd protocol summary - statistics. - * Allow file additions/deletion to be communicated to the XrdCnsd so that is - can maintain an accurate inventory. This update adds the frm.all.cnsd - directive which specifies how the information is to be commuincated. - * Enable cmsd monitoring. For now, only [meta]manager information is reported. - * Add new repstats config directive to increase reporting detail. - * New class, XrdCmsRole, to make role naming/handling consistent. - * Implement the 'cms.delay qdn' directive which allows one to tell the - 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 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. - * Include additional information in the protocol statistics. - * Resize some counters to prevent overflows. - * Add the 'cms.delay qdn' directive to allow better redirection control in - the future. - * Allow a plugin (notably the proxy plugin) to disable async I/O. - * Implement a general memory caching object. Currently, this will be used - by the Posix object. - * Allow optional memory caching when using the Posix library. This is - primarily used by the proxy object to reduce trips to a data server when - small blocks are accessed via the proxy server. This requires - configuration using the new 'pss.memcache' directive. - * 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 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. - This also added a more effecient lfn2pfn() method to the storage system. - * Allow native checksums to be enabled in the xrootd layer. - See the xrootd.chksum directive on how to do this. - * Add checksum management to the frm_admin command. - * Allow XrdOucProg to dispatch a local program as well as a process. - * Allow a line to be insrerted into an XrdOucStream managed stream. - * Implement native checksums usable stand-alone or as plugins. Three digests - are supported: adler32, crc32, and md5. An additional digest can be added - via a plugin. Also, the native digests can be over-ridden via a plugin. - * In XrdSecgsi, new interface for the authorization plug-in which has now full - access to the XrdSecEntity object, with the possibility to fill/modify all the - fields according to the proxy chain. The plug-in is now called at the end of - the all process, after a successful handshake and DN-username mapping. - Implementations must contain three extern C functions; see the dummy example - provided in src/XrdSecgsi/XrdSecgsiAuthzFunDN.cc. - See also the header of XrdSecProtocolgsi::LoadAuthzFun. - * In XrdCryptosslgsiAux, add function to extract the VOMS attributes; can be - used in authz plug-ins. - * In XrdSecgsi, add possibility to extract the VOMS attributes and save them - in the XrdSecEntity. New switch '-vomsat:0/1 [1]'. - * In 'xrdgsiproxy info' show also the VOMS attributes, if present. - * Automatically build the RPM for the xrootd user when an OSG build is detected - and add fedora > 15 init scripts dependencies - -+ **Major bug fixes** - * Do not close the loger's shadow file descriptor when backgrounding as - this may cause random crashes later on. - * Avoid SEGV by setting network pointer prior to loading the 1st protocol. - * Enforce r/o path during mkdir operations. - * Avoid segv when initializing the finder on a multi-core machine. - * Fix incorrect lock handling for multiple waiters. - * Fix possible deadlocks in XrdSutCache preventing the pwd security module - to work correctly - -+ **Minor bug fixes** - * Properly handle the case when a site has an excessive number of groups - assignments. - * Prevent the response to a query from being truncated on the client side. - * Report readv information in the detailed monitoring stream. - * Correct default settings due to feature interactions after the fact. Now, - oss.defaults acts as if the setting were actually specified via oss.export. - * Actually use the N2N library of specified or implied via pss.localroot - for proxy server interactions withthe origin (required for Atlas T2). - * Use re-enterant versions of getpwuid() and getpwgid(). This is need for - FUSE. - * Correct bad english in a few error messages. - * Set correct checksum length when converting ASCII to binary. - * Allow the sss protocol to work for multi-homed hosts. - * Correct definition of AtomicISM that caused the maximum link count to - never be updated in the statistics. - * Apply N2N mapping to source path when relocating the file. - * Report correct port when locate is directly issued to a data server - (before it was being reported as 0). - * Make the default file system a pointer to a dynamic instance of XrdOfs - instead of a global static (i.e. the Andreas Peters patch). This makes - writing an ofs plugin easier. - * Fix the RPM uninstall scriptlets incorrectly invoking /sbin/ldconfig. - * Install XrdOlbMonPerf and netchk tools. - * Fix a bug preventing the core of authentication errors to be logged to clients - * In the krb5 security plugin, define KRB5CCNAME to point to the credential - cache file /tmp/krb5cc_ only if this file exists and is readable. - Solves an issue with credentials cached in memory (API::n). - * Fix array deletion mismatches reported by cppcheck (from D. Volgyes) - * Make sure that loading of XrdSecgsi.so fails if either the GMAPFun or the - AuthzFun plug-ins fail to load. - -+ **Miscellaneous** - * Drop Windows support. - * Code cleanup: remove XrdTokenAuthzOfs, simple tests, broken utilities, - the gridftp code, krb4 and secssl plugins, obsolete documentation files - * Make the loadable module extensions configurable depending on the platform - (so on Linux and Solaris, dylib on MacOs) - * Add new XrdVNUMBER macro. - * Clean up the conditional compilation macros. - * Remove compression related attributes (compchk, ssdec) and directives - (compdetect) as they were never used nor fully implemented. - * Remove the userprty directive. It was deprecated and never specified. - * Refactor PosixPreeload and Posix libraries to prevent split initialization - of the preload library which will cause failures on certain systems. - * Provide automatic proxy checksum defaults when role is set to proxy. - * Remove all references via extern statements to object instances. This - only applies to the Xrd package. - * Do not echo lines qualified by an in-line if when the if fails. - * Remove the old "redirect" directive. It has passed its prime. - * Remove back references to symbols defined in XrdXrootd package used by - the cms client to allow for clean shared library builds. - * Remove externs to XrdSecGetProtocol and XrdSecGetService from - XrdSecInterface.hh to avoid having undefined references just because the - include file was included somewhere. - * Rename XrdNetDNS to XrdSysDNS to avoid cross-dependencies. This means that all - plug-in developers will need to do the same as XrdNetDNS no longer exists. - * Split XrdFrm into XrdFrm and XrdFrc. This prevents cross-dependencies in - packages that use the File Residency Manager. - -------------- -Version 3.0.5 -------------- - -+ **Major bug fixes** - * Avoid stage failures when target file exists in purgeable or writable space. - * Make sure all the threads are joined when closing a physical connection. - * Fix free/delete mismatch in XrdSecProtocolgsi et al. - -+ **Minor bug fixes** - * Remove old async shutdown workaround patch introduced in Linux 2.3. The - problem has been since fixed and the solution now causes problems. - * Install the netchk tool - -------------- -Version 3.0.4 -------------- - -+ **New features** - * xrdcp now has -version parameter - * xrdcp automatically ads the oss.asize hint to the url opaque data. - This functionality may be disabled by setting the XrdCpSizeHint - variable to 0 (XRD_XRDCPSIZEHIN in the shell). - * The client will try to resolve the server hostname on every retry to - enable DNS failovers. - * RPM: devel package split into libs-devel, client-devel and server-devel - * XrootdFS: all paramenters can be passed via command line, add -h. - * Allow a plugin (notably the proxy plugin) to disable async I/O. - * New class XrdSysRWLock interfacing the pthread_rwlock functionality - * In XrdSecEntity: Add new fields 'creds' and 'credslen' to be filled - with the raw client credentials - * In XrdSutCache: Use XrdSysRWLock to coordinate concurrent access to - the cache - * In XrdSecgsi: - - - Add option to have Entity.name filled with the plain DN, instead of - the DN hash, when no mapping is requested or found. - - - Enable cache also for authz mapping results. - - - Do not require the existence of a grid-mapfile if gmapopt=2 and there is at least - a gmapfun or an authzfun defined. - - - Add example of mapping function allowing to match parts of the DN - - - Extend existing option 'authzpxy' to allow exporting the incoming client credentials in - XrdSecEntity. - -+ **Major bug fixes** - * Async write errors are now being properly caught and reacted to. - XrdClient::Close will now fail if it cannot recover from async - write errors. - * xrdcp prints an error message and returns failure to the shell - when some of the write requests it issues fail. - * libXrdPosixPreload now builds with autotools and is included into - the xrootd-client RPM - * RPM: FFS moved from libs to client - * Properly parse oss.asize. This because a major problem when xrdcp started - adding this to the url which causes the copy to fail. - * Spin connection portion of proxy initialization to a background thread. - This prevents init.d hangs when a redirector is not available. - -+ **Minor bug fixes** - * Test for 64-bit atomics instead 32-bit ones. Fixes build on 32-bit PowerPC. - * RPM: xrootd-fuse now depends on fuse - * Take correctly into accoutn summer time in calculating the time left for - a proxy validity - * Add support for Ubuntu 11 which uses the directory /usr/lib/`dpkg-architecture - -qDEB_HOST_MULTIARCH` to store platform dependent libraries. - -------------- -Version 3.0.3 -------------- - -+ **New features** - * Change configure.classic to handle various versions of processors in a - more sane way (this fixes several Solaris issues and atomics for i686). - * Add fwdwait option to cms.request directive to allow pacing of forwarded - requests (off by default). - * Use native memory synchronization primitives when available when doing - network I/O. This will eventually be extended to cover all other cases. - * Add the qdl option to the cms.delay directive to allow changing the - query window independently of the time a client is asked to wait for the - query to actually complete. - * Add 'pss.namelib' directive to allow proxies to pre-translate the lfn - for servers that cannot do so (e.g., dCache xrootd door). - * Optimize handling of shared-everything ile systems (e.g., dCache, GPFS, - DFS, Lustre, etc.) in the cmsd. - * Implement optional throttling for meta-manager requests in the cmsd. - * New cmsd directive, cms.dfs, declares that the underlying file system - is a shared-everything system (i.e., distributed file system) and allow - for optimal configuration and meta-manager throttling. - * Change the oss and fm components to use file extended attributes instead - of meta-files. This affects copy, create, reloc, rename, and unlink in the - oss layer. Migrate, purge, transfer, and most admin commands in the frm - component. The 'all.export' directive now accepts the noxattr/xattr option. - WARNING: If the migrator or purge options have been specified for any path - in the 'all.export; directive then this change requires either the the - 'oss.runmodeold' directive be added to the configuration file to provide - backward compatibility or that the name and data spaces be migrated using - the frm_admin command. See "Migrating tp Extended Attributes" manual for - detailed information and the new 'frm_admin convert' subcommand. - * Avoid physical copy if the operation can be handled using hard links. This - greatly speeds up static space token reassignment. - * Add platform independent interface to extended file attributes. - * RPM packaging and Red Hat Enterprise Linux compatible init scripts - capable of handling multiple instances of the xrootd daemons. The instances - can be defined in the /etc/sysconfig/xrootd file and then handled using standard:: - - service xrootd start|stop|... - service cmsd start|stop|... - ... - - or handled by name:: - - service xrootd start instance1 instance5 - - * New '-s' commandline option for xrootd, cmsd, frm_purged and frm_xfrd - creating a pidfile. - * xrootd, cmsd, frm_purged and frm_xfrd now return failure to the shell - when called with '-b' option (daemonization) and the daemon fails to - initialize. - * New 'EnableTCPKeepAlive' client environment option added enabling the TCP - stack keep-alive functionality for the sockets. - On Linux three addtional fine-tunning options are available: - - - TCPKeepAliveTime - interval (in seconds) between the last data packet and the first keep-alive - probe - - TCPKeepAliveInterval - interval (in seconds) between the probes - - TCPKeepAliveProbes - number of probes lost to consider the connection broken - - * New functionality handling process forking. When enabled (via the 'EnableForkHandlers' - env option) prior to a call to fork it shuts down all the xrootd connection management - facilities (including the connections themselves) and reinitializes them after the fork - both in the parent and the child process. This ensures relative fork safety provided - that all the XrdClient and XrdAdmin instances are closed when the fork function is invoked. - -+ **Major bug fixes** - * Add missing braces that caused config failure in frm_admin command. - * Account for correct path when -M value is zero in hpsscp command. - * In XrdCryptossl, fix for thread-safeness; solves random crashes observed on the - server side under high authentication frequency - * In XrdOucBonjour, fix important issue with host domain name registration, preventing - the correct domain to be posted. - -+ **Minor bug fixes** - * Correct file discovery propogation for proxy manager relative to meta-managers. - * Correct oss partition selection algorithm to further spread out file - allocation. - * Allow underscores in set/setenv variables. - * Add null byte after checksum value response. - * Move mapping of errno to xrootd error code to the protocol package where it - belongs. This also removes a cross dependency. - * Correct RetToken() behaviour in the presence of multiple spaces between tokens and - the previous call returned the remainder of the line (very obscure circumstances). - * [bug #77535] xrdcp now returns an error to the shell when it fails to copy the file - * [bug #79710] xrdcp now gracefully aborts when it encounters a corrupted local file - * Reset the transaction timeout for the Query method. - This fixes transaction timeout issues for clients doing only queries. - * Rename variable to remove conflict between it and global of the same name. - * Fix frm_admin command line option parsing so it does not trip over - subcommand options. This also fixes a SEGV in MacOS when this actually - happens. - * Enable the '-md5' option when OpenSSL is present and xrootd is built with autotools. - -+ **Documentation** - * Added man pages for: xprep, xrd, xrdcp, xrdstagetool, xrdgsiproxy - -------------- -Version 3.0.2 -------------- - -+ **Minor bug fixes** - * Fix the build on Solaris 10. - * Fix the build on SLC4. - * Fix the out-of-the-source-tree builds with autotools. - * Fix a segfault while doing a recursive copy from root:// to root://. - -------------- -Version 3.0.1 -------------- - -+ **New features** - * New application, cconfing, added to display configuration files relative to a host-program-instance. - * New application, netchk, that tests that firewalls have been correctly setup. - * New configure.classic option to allow use of stl4port library for Solaris. - * New internal feature in XrdPosix library to not shadow files with actual file descriptors (used by the proxy - service). This increases scalability. - * Allow the xrootd server to tell the client that it is a meta-manager. - * Support fo proxies generated by Globus version 4.2.1 in libXrdSecssl. - -+ **Major bug fixes** - * Change link options for xrdadler32 to not use shared libraries. The previous setup caused the command to hang - upon exit. - * Remove instance of XrdPosixXrootd from that same file. Including it disallows defaults from being changed. - -+ **Minor bug fixes** - * Fix XrdOucStream to not return ending "fi". - * Correct network option interference -- do not turn on network nodnr option should the keepalive option - be specified. - * Remove duplicate option in option table used by the proxy service. - * Compile on Solaris 11 Express using SunCC. - * Compile on Windows using MSVC++2010. diff --git a/src/XrdCeph/packaging/debian/compat b/src/XrdCeph/packaging/debian/compat deleted file mode 100644 index f599e28b8ab..00000000000 --- a/src/XrdCeph/packaging/debian/compat +++ /dev/null @@ -1 +0,0 @@ -10 diff --git a/src/XrdCeph/packaging/debian/control b/src/XrdCeph/packaging/debian/control deleted file mode 100644 index 86b8341286f..00000000000 --- a/src/XrdCeph/packaging/debian/control +++ /dev/null @@ -1,48 +0,0 @@ -Source: xrootd -Maintainer: Jozsef Makai -Section: misc -Priority: optional -Standards-Version: 3.9.3 -Build-Depends: debhelper (>= 9), cmake (>=3.3.0), zlib1g-dev, libfuse-dev, python-dev, libssl-dev, libxml2-dev, ncurses-dev, libkrb5-dev, libreadline-dev, libsystemd-dev, selinux-policy-dev, systemd -Homepage: https://github.com/xrootd/xrootd -Vcs-Git: https://github.com/xrootd/xrootd.git -Vcs-Browser: https://github.com/xrootd/xrootd - -Package: xrootd-libs -Architecture: any -Description: This package contains libraries used by the xrootd servers and clients. - -Package: xrootd-devel -Architecture: any -Depends: ${shlibs:Depends}, xrootd-libs (=${binary:Version}) -Description: This package contains header files and development libraries for xrootd development. - -Package: xrootd-client-libs -Architecture: any -Depends: ${shlibs:Depends}, xrootd-libs (=${binary:Version}) -Description: This package contains libraries used by xrootd clients. - -Package: xrootd-client-devel -Architecture: any -Depends: ${shlibs:Depends}, xrootd-devel (=${binary:Version}), xrootd-client-libs (=${binary:Version}) -Description: This package contains header files and development libraries for xrootd client development. - -Package: xrootd-client -Architecture: any -Depends: ${shlibs:Depends}, libxml2, xrootd-libs (=${binary:Version}), xrootd-client-libs (=${binary:Version}) -Description: This package contains the command line tools used to communicate with xrootd servers. - -Package: xrootd-private-devel -Architecture: any -Depends: ${shlibs:Depends}, xrootd-libs (=${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. - -Package: xrootd-server-libs -Architecture: any -Depends: ${shlibs:Depends}, xrootd-libs (=${binary:Version}), xrootd-client-libs (=${binary:Version}) -Description: This package contains libraries used by xrootd servers. - -Package: xrootd-server-devel -Architecture: any -Depends: ${shlibs:Depends}, xrootd-devel (=${binary:Version}), xrootd-client-devel (=${binary:Version}), xrootd-server-libs (=${binary:Version}) -Description: This package contains header files and development libraries for xrootd server development. diff --git a/src/XrdCeph/packaging/debian/copyright b/src/XrdCeph/packaging/debian/copyright deleted file mode 100644 index 8fd5621be2c..00000000000 --- a/src/XrdCeph/packaging/debian/copyright +++ /dev/null @@ -1,18 +0,0 @@ -"Copyright (c) 2005-2012, Board of Trustees of the Leland Stanford, Jr. University.\n" -"Produced under contract DE-AC02-76-SF00515 with the US Department of Energy. \n" -"All rights reserved. The copyright holder's institutional names may not be used to\n" -"endorse or promote products derived from this software without specific prior \n" -"written permission.\n\n" -"This file is part of the XRootD software suite. \n\n" -"XRootD is free software: you can redistribute it and/or modify it under the terms \n" -"of the GNU Lesser General Public License as published by the Free Software \n" -"Foundation, either version 3 of the License, or (at your option) any later version.\n\n" -"XRootD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\n" -"without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR \n" -"PURPOSE. See the GNU Lesser General Public License for more details. \nn" -"You should have received a copy of the GNU Lesser General Public License along \n" -"with XRootD in a file called COPYING.LESSER (LGPL license) and file COPYING (GPL \n" -"license). If not, see .\n\n" -"Prior to September 2nd, 2012 the XRootD software suite was licensed under a\n" -"modified BSD license (see file COPYING.BSD). This applies to all code that\n" -"was in the XRootD git repository prior to that date.\n" diff --git a/src/XrdCeph/packaging/debian/rules b/src/XrdCeph/packaging/debian/rules deleted file mode 100755 index a89c9970c54..00000000000 --- a/src/XrdCeph/packaging/debian/rules +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/make -f - -%: - dh $@ --builddirectory=build --destdir=deb_packages - -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) - -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 - dh_install --sourcedir=deb_packages diff --git a/src/XrdCeph/packaging/debian/source/format b/src/XrdCeph/packaging/debian/source/format deleted file mode 100644 index 163aaf8d82b..00000000000 --- a/src/XrdCeph/packaging/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/src/XrdCeph/packaging/debian/xrootd-client-devel.install b/src/XrdCeph/packaging/debian/xrootd-client-devel.install deleted file mode 100644 index 321cbc0430c..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-client-devel.install +++ /dev/null @@ -1,9 +0,0 @@ -usr/bin/xrdgsitest -usr/lib/*/libXrdCl.so -usr/lib/*/libXrdClient.so -usr/lib/*/libXrdFfs.so -usr/lib/*/libXrdPosix.so -usr/share/man/man1/xrdgsitest.1* -usr/include/xrootd/XrdCl -usr/include/xrootd/XrdClient -usr/include/xrootd/XrdPosix diff --git a/src/XrdCeph/packaging/debian/xrootd-client-libs.install b/src/XrdCeph/packaging/debian/xrootd-client-libs.install deleted file mode 100644 index a8a45bfa66c..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-client-libs.install +++ /dev/null @@ -1,8 +0,0 @@ -usr/lib/*/libXrdCl.so.2* -usr/lib/*/libXrdClient.so.2* -usr/lib/*/libXrdFfs.so.2* -usr/lib/*/libXrdPosix.so.2* -usr/lib/*/libXrdPosixPreload.so.1* -usr/lib/*/libXrdPosixPreload.so -etc/xrootd/client.plugins.d/client-plugin.conf.example -etc/xrootd/client.conf diff --git a/src/XrdCeph/packaging/debian/xrootd-client-libs.postinst b/src/XrdCeph/packaging/debian/xrootd-client-libs.postinst deleted file mode 100644 index 2983b531b1f..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-client-libs.postinst +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -ldconfig diff --git a/src/XrdCeph/packaging/debian/xrootd-client-libs.postrm b/src/XrdCeph/packaging/debian/xrootd-client-libs.postrm deleted file mode 100644 index 2983b531b1f..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-client-libs.postrm +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -ldconfig diff --git a/src/XrdCeph/packaging/debian/xrootd-client.install b/src/XrdCeph/packaging/debian/xrootd-client.install deleted file mode 100644 index d4ca23ae269..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-client.install +++ /dev/null @@ -1,18 +0,0 @@ -usr/bin/xprep -usr/bin/xrd -usr/bin/xrdadler32 -usr/bin/xrdcopy -usr/bin/xrdcp -usr/bin/xrdcp-old -usr/bin/xrdfs -usr/bin/xrdgsiproxy -usr/bin/xrdstagetool -usr/share/man/man1/xprep.1* -usr/share/man/man1/xrd.1* -usr/share/man/man1/xrdadler32.1* -usr/share/man/man1/xrdcopy.1* -usr/share/man/man1/xrdcp.1* -usr/share/man/man1/xrdcp-old.1* -usr/share/man/man1/xrdfs.1* -usr/share/man/man1/xrdgsiproxy.1* -usr/share/man/man1/xrdstagetool.1* diff --git a/src/XrdCeph/packaging/debian/xrootd-devel.install b/src/XrdCeph/packaging/debian/xrootd-devel.install deleted file mode 100644 index 8ce0c9c9a9e..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-devel.install +++ /dev/null @@ -1,15 +0,0 @@ -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 diff --git a/src/XrdCeph/packaging/debian/xrootd-libs.install b/src/XrdCeph/packaging/debian/xrootd-libs.install deleted file mode 100644 index 8faf68a3046..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-libs.install +++ /dev/null @@ -1,9 +0,0 @@ -usr/lib/*/libXrdAppUtils.so.1* -usr/lib/*/libXrdClProxyPlugin-4.so -usr/lib/*/libXrdCks*-4.so -usr/lib/*/libXrdCrypto.so.1* -usr/lib/*/libXrdCryptoLite.so.1* -usr/lib/*/libXrdCryptossl-4.so -usr/lib/*/libXrdSec*-4.so -usr/lib/*/libXrdUtils.so.* -usr/lib/*/libXrdXml.so.* diff --git a/src/XrdCeph/packaging/debian/xrootd-libs.postinst b/src/XrdCeph/packaging/debian/xrootd-libs.postinst deleted file mode 100644 index 2983b531b1f..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-libs.postinst +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -ldconfig diff --git a/src/XrdCeph/packaging/debian/xrootd-libs.postrm b/src/XrdCeph/packaging/debian/xrootd-libs.postrm deleted file mode 100644 index 2983b531b1f..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-libs.postrm +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -ldconfig diff --git a/src/XrdCeph/packaging/debian/xrootd-private-devel.install b/src/XrdCeph/packaging/debian/xrootd-private-devel.install deleted file mode 100644 index 38c034f2bb2..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-private-devel.install +++ /dev/null @@ -1,3 +0,0 @@ -usr/include/xrootd/private -usr/lib/*/libXrdSsiLib.so -usr/lib/*/libXrdSsiShMap.so diff --git a/src/XrdCeph/packaging/debian/xrootd-server-devel.install b/src/XrdCeph/packaging/debian/xrootd-server-devel.install deleted file mode 100644 index d1dcf33b6c5..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-server-devel.install +++ /dev/null @@ -1,8 +0,0 @@ -usr/include/xrootd/XrdAcc -usr/include/xrootd/XrdCms -usr/include/xrootd/XrdFileCache -usr/include/xrootd/XrdOss -usr/include/xrootd/XrdSfs -usr/include/xrootd/XrdXrootd -usr/include/xrootd/XrdHttp -usr/lib/*/libXrdServer.so diff --git a/src/XrdCeph/packaging/debian/xrootd-server-libs.install b/src/XrdCeph/packaging/debian/xrootd-server-libs.install deleted file mode 100644 index de9c8be9e28..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-server-libs.install +++ /dev/null @@ -1,14 +0,0 @@ -usr/lib/*/libXrdBwm-4.so -usr/lib/*/libXrdPss-4.so -usr/lib/*/libXrdXrootd-4.so -usr/lib/*/libXrdFileCache-4.so -usr/lib/*/libXrdBlacklistDecision-4.so -usr/lib/*/libXrdHttp-4.so -usr/lib/*/libXrdN2No2p-4.so -usr/lib/*/libXrdOssSIgpfsT-4.so -usr/lib/*/libXrdServer.so.* -usr/lib/*/libXrdSsi-4.so -usr/lib/*/libXrdSsiLib.so.* -usr/lib/*/libXrdSsiLog-4.so -usr/lib/*/libXrdSsiShMap.so.* -usr/lib/*/libXrdThrottle-4.so diff --git a/src/XrdCeph/packaging/debian/xrootd-server-libs.postinst b/src/XrdCeph/packaging/debian/xrootd-server-libs.postinst deleted file mode 100644 index 2983b531b1f..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-server-libs.postinst +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -ldconfig diff --git a/src/XrdCeph/packaging/debian/xrootd-server-libs.postrm b/src/XrdCeph/packaging/debian/xrootd-server-libs.postrm deleted file mode 100644 index 2983b531b1f..00000000000 --- a/src/XrdCeph/packaging/debian/xrootd-server-libs.postrm +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -ldconfig diff --git a/src/XrdCeph/packaging/debian_scripts/publish_debian_cern.sh b/src/XrdCeph/packaging/debian_scripts/publish_debian_cern.sh deleted file mode 100755 index 204914cec25..00000000000 --- a/src/XrdCeph/packaging/debian_scripts/publish_debian_cern.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -#------------------------------------------------------------------------------- -# Publish debian artifacts on CERN Gitlab CI -# Author: Jozsef Makai (11.08.2017) -#------------------------------------------------------------------------------- - -set -e - -comp=$1 -prefix=/eos/project/s/storage-ci/www/debian/xrootd - -for dist in artful xenial; do - echo "Publishing for $dist"; - path=$prefix/pool/$dist/$comp/x/xrootd/; - mkdir -p $path; - if [[ "$comp" == "master" ]]; then find ${path} -type f -name '*deb' -delete; fi - cp $dist/*deb $path; - mkdir -p $prefix/dists/$dist/$comp/binary-amd64/; - (cd $prefix && apt-ftparchive --arch amd64 packages pool/$dist/$comp/ > dists/$dist/$comp/binary-amd64/Packages); - gzip -c $prefix/dists/$dist/$comp/binary-amd64/Packages > $prefix/dists/$dist/$comp/binary-amd64/Packages.gz; - components=$(find $prefix/dists/$dist/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | tr '\n' ' ') - if [ -e $prefix/dists/$dist/Release ]; then - rm $prefix/dists/$dist/Release - fi - if [ -e $prefix/dists/$dist/InRelease ]; then - rm $prefix/dists/$dist/InRelease - fi - if [ -e $prefix/dists/$dist/Release.gpg ]; then - rm $prefix/dists/$dist/Release.gpg - fi - apt-ftparchive -o APT::FTPArchive::Release::Origin=CERN -o APT::FTPArchive::Release::Label=XrootD -o APT::FTPArchive::Release::Codename=artful -o APT::FTPArchive::Release::Architectures=amd64 -o APT::FTPArchive::Release::Components="$components" release $prefix/dists/$dist/ > $prefix/dists/$dist/Release; - gpg --homedir /home/stci/ --clearsign -o $prefix/dists/$dist/InRelease $prefix/dists/$dist/Release; - gpg --homedir /home/stci/ -abs -o $prefix/dists/$dist/Release.gpg $prefix/dists/$dist/Release; -done diff --git a/src/XrdCeph/packaging/makesrpm.sh b/src/XrdCeph/packaging/makesrpm.sh deleted file mode 100755 index b74b90fd471..00000000000 --- a/src/XrdCeph/packaging/makesrpm.sh +++ /dev/null @@ -1,256 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------- -# Create a source RPM package -# 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 "`which $prog 2>/dev/null`"; then - echo $prog - break - fi - done -} - -#------------------------------------------------------------------------------- -# Print help -#------------------------------------------------------------------------------- -function printHelp() -{ - echo "Usage:" 1>&2 - echo "${0} [--help] [--source PATH] [--output PATH]" 1>&2 - echo " --help prints this message" 1>&2 - echo " --source PATH specify the root of the source tree" 1>&2 - echo " defaults to ../" 1>&2 - echo " --output PATH the directory where the source rpm" 1>&2 - echo " should be stored, defaulting to ." 1>&2 - echo " --version VERSION the version provided by user" 1>&2 - echo " --define 'MACRO EXPR'" 1>&2 -} - -#------------------------------------------------------------------------------- -# Parse the commandline, if only we could use getopt... :( -#------------------------------------------------------------------------------- -SOURCEPATH="../" -OUTPUTPATH="." -PRINTHELP=0 - -while test ${#} -ne 0; do - if test x${1} = x--help; then - PRINTHELP=1 - elif test x${1} = x--source; then - if test ${#} -lt 2; then - echo "--source parameter needs an argument" 1>&2 - exit 1 - fi - SOURCEPATH=${2} - shift - elif test x${1} = x--output; then - if test ${#} -lt 2; then - echo "--output parameter needs an argument" 1>&2 - exit 1 - fi - OUTPUTPATH=${2} - shift - 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="--version ${2}" - shift - elif test x${1} = x--define; then - if test ${#} -lt 2; then - echo "--define parameter needs an argument" 1>&2 - exit 1 - fi - USER_DEFINE="$USER_DEFINE --define \""${2}"\"" - shift - fi - shift -done - -if test $PRINTHELP -eq 1; then - printHelp - exit 0 -fi - -echo "[i] Working on: $SOURCEPATH" -echo "[i] Storing the output to: $OUTPUTPATH" - -#------------------------------------------------------------------------------- -# Check if the source and the output dirs -#------------------------------------------------------------------------------- -if test ! -d $SOURCEPATH -o ! -r $SOURCEPATH; then - echo "[!] Source path does not exist or is not readable" 1>&2 - exit 2 -fi - -if test ! -d $OUTPUTPATH -o ! -w $OUTPUTPATH; then - echo "[!] Output path does not exist or is not writeable" 1>&2 - exit 2 -fi - -#------------------------------------------------------------------------------- -# Check if we have all the necassary components -#------------------------------------------------------------------------------- -if test x`findProg rpmbuild` = x; then - echo "[!] Unable to find rpmbuild, aborting..." 1>&2 - exit 1 -fi - -if test x`findProg git` = x; then - echo "[!] Unable to find git, aborting..." 1>&2 - exit 1 -fi - -#------------------------------------------------------------------------------- -# Check if the source is a git repository -#------------------------------------------------------------------------------- -if test ! -d $SOURCEPATH/.git; then - echo "[!] I can only work with a git repository" 1>&2 - 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=`$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 -fi - -echo "[i] Working with version: $VERSION" - -if test x${VERSION:0:1} = x"v"; then - VERSION=${VERSION:1} -fi - -#------------------------------------------------------------------------------- -# Deal with release candidates -#------------------------------------------------------------------------------- -RELEASE=1 -if test x`echo $VERSION | egrep $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 | egrep $CERNEXP` != x; then - RELEASE=`echo $VERSION | sed 's/.*-//'` - VERSION=`echo $VERSION | sed 's/-.*\.CERN//'` -fi - -#------------------------------------------------------------------------------- -# 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 - -VERSION=`echo $VERSION | sed 's/-/./g'` -echo "[i] RPM compliant version: $VERSION-$RELEASE" - -#------------------------------------------------------------------------------- -# Create a tempdir and copy the files there -#------------------------------------------------------------------------------- -# exit on any error -set -e - -TEMPDIR=`mktemp -d /tmp/xrootd-ceph.srpm.XXXXXXXXXX` -RPMSOURCES=$TEMPDIR/rpmbuild/SOURCES -mkdir -p $RPMSOURCES -mkdir -p $TEMPDIR/rpmbuild/SRPMS - -echo "[i] Working in: $TEMPDIR" 1>&2 - -if test -d rhel -a -r rhel; then - for i in rhel/*; do - cp $i $RPMSOURCES - done -fi - -if test -d common -a -r common; then - for i in common/*; do - cp $i $RPMSOURCES - done -fi - -#------------------------------------------------------------------------------- -# Generate the spec file -#------------------------------------------------------------------------------- -if test ! -r rhel/xrootd-ceph.spec.in; then - echo "[!] The specfile template does not exist!" 1>&2 - exit 7 -fi -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 -#------------------------------------------------------------------------------- -# no more exiting on error -set +e - -CWD=$PWD -cd $SOURCEPATH -COMMIT=`git log --pretty=format:"%H" -1` - -if test $? -ne 0; then - echo "[!] Unable to figure out the git commit hash" 1>&2 - exit 5 -fi - -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 - -cd $CWD - -#------------------------------------------------------------------------------- -# Build the source RPM -#------------------------------------------------------------------------------- -echo "[i] Creating the source RPM..." - -# Dirty, dirty hack! -echo "%_sourcedir $RPMSOURCES" >> $TEMPDIR/rpmmacros -eval "rpmbuild --define \"_topdir $TEMPDIR/rpmbuild\" \ - --define \"%_sourcedir $RPMSOURCES\" \ - --define \"%_srcrpmdir %{_topdir}/SRPMS\" \ - --define \"_source_filedigest_algorithm md5\" \ - --define \"_binary_filedigest_algorithm md5\" \ - ${USER_DEFINE} \ - -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-ceph*.src.rpm $OUTPUTPATH -rm -rf $TEMPDIR - -echo "[i] Done." diff --git a/src/XrdCeph/packaging/rhel/xrootd-ceph.spec.in b/src/XrdCeph/packaging/rhel/xrootd-ceph.spec.in deleted file mode 100644 index 03b1caccebd..00000000000 --- a/src/XrdCeph/packaging/rhel/xrootd-ceph.spec.in +++ /dev/null @@ -1,167 +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 - -%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 -#------------------------------------------------------------------------------- -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 - -%if %{use_cmake3} -BuildRequires: cmake3 -%else -BuildRequires: cmake -%endif - -%if %{?_with_tests:1}%{!?_with_tests:0} -BuildRequires: cppunit-devel -%endif - -BuildRequires: librados-devel = 2:14.2.15 -BuildRequires: libradosstriper-devel = 2:14.2.15 - -%if %{?_with_clang:1}%{!?_with_clang:0} -BuildRequires: clang -%endif - -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} - -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 -with the Ceph storage platform. - -#------------------------------------------------------------------------------- -# 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 - -%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 \ -%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-5.so -%{_libdir}/libXrdCephXattr-5.so -%{_libdir}/libXrdCephPosix.so* - -%if %{?_with_tests:1}%{!?_with_tests:0} -%files tests -%defattr(-,root,root,-) -%{_libdir}/libXrdCephTests*.so -%endif - -#------------------------------------------------------------------------------- -# 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 -- initial release diff --git a/src/XrdCeph/src/CMakeLists.txt b/src/XrdCeph/src/CMakeLists.txt deleted file mode 100644 index b044ee25fec..00000000000 --- a/src/XrdCeph/src/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ - -#------------------------------------------------------------------------------- -# 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/src/XrdCeph.cmake b/src/XrdCeph/src/XrdCeph.cmake deleted file mode 100644 index 5277df3fc5b..00000000000 --- a/src/XrdCeph/src/XrdCeph.cmake +++ /dev/null @@ -1,74 +0,0 @@ -#------------------------------------------------------------------------------- -# XrdCephPosix library version -#------------------------------------------------------------------------------- -set( XRD_CEPH_POSIX_VERSION 0.0.1 ) -set( XRD_CEPH_POSIX_SOVERSION 0 ) - -#------------------------------------------------------------------------------- -# The XrdCephPosix library -#------------------------------------------------------------------------------- -add_library( - XrdCephPosix - SHARED - XrdCeph/XrdCephPosix.cc XrdCeph/XrdCephPosix.hh ) - -# needed during the transition between ceph giant and ceph hammer -# for object listing API -set_property(SOURCE XrdCeph/XrdCephPosix.cc - PROPERTY COMPILE_FLAGS " -Wno-deprecated-declarations") - -target_link_libraries( - XrdCephPosix - PRIVATE - ${XROOTD_LIBRARIES} - ${RADOS_LIBS} ) - -target_include_directories( - XrdCephPosix PUBLIC ${RADOS_INCLUDE_DIR} $) - -set_target_properties( - XrdCephPosix - PROPERTIES - VERSION ${XRD_CEPH_POSIX_VERSION} - SOVERSION ${XRD_CEPH_POSIX_SOVERSION} ) - -#------------------------------------------------------------------------------- -# The XrdCeph module -#------------------------------------------------------------------------------- -set( LIB_XRD_CEPH XrdCeph-${PLUGIN_VERSION} ) - -add_library( - ${LIB_XRD_CEPH} - MODULE - XrdCeph/XrdCephOss.cc XrdCeph/XrdCephOss.hh - XrdCeph/XrdCephOssFile.cc XrdCeph/XrdCephOssFile.hh - XrdCeph/XrdCephOssDir.cc XrdCeph/XrdCephOssDir.hh ) - -target_link_libraries( - ${LIB_XRD_CEPH} - PRIVATE - ${XROOTD_LIBRARIES} - XrdCephPosix ) - -#------------------------------------------------------------------------------- -# The XrdCephXattr module -#------------------------------------------------------------------------------- -set( LIB_XRD_CEPH_XATTR XrdCephXattr-${PLUGIN_VERSION} ) - -add_library( - ${LIB_XRD_CEPH_XATTR} - MODULE - XrdCeph/XrdCephXAttr.cc XrdCeph/XrdCephXAttr.hh ) - -target_link_libraries( - ${LIB_XRD_CEPH_XATTR} - PRIVATE - ${XROOTD_LIBRARIES} - XrdCephPosix ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS ${LIB_XRD_CEPH} ${LIB_XRD_CEPH_XATTR} XrdCephPosix - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/src/XrdCeph/src/XrdVersion.hh.in b/src/XrdCeph/src/XrdVersion.hh.in deleted file mode 100644 index 8ccbfa4c07b..00000000000 --- a/src/XrdCeph/src/XrdVersion.hh.in +++ /dev/null @@ -1,106 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d V e r s i o n . h h . i n */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's 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 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" - -// 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 - -#if XrdDEBUG -#define XrdVSTRING XrdVERSION "_dbg" -#else -#define XrdVSTRING XrdVERSION -#endif - -// The following defines the shared library version number of any plug-in. -// Generally, all plug-ins have a uniform version number releative to a -// specific compilation. This version is appended to the so-name and for -// dylibs becomes part of he actual filename (MacOS format). -// -#ifndef XRDPLUGIN_SOVERSION -#define XRDPLUGIN_SOVERSION "4" -#endif - -#define XrdDEFAULTPORT 1094; - -// The following macros extract version digits from a numeric version number -#define XrdMajorVNUM(x) x/10000 -#define XrdMinorVNUM(x) x/100%100 -#define XrdPatchVNUM(x) x%100 - -// The following structure defines the standard way to record a version. You can -// determine component version numbers within an object file by simply executing -// "strings | grep '@V:'". -// -struct XrdVersionInfo {int vNum; const char vOpt; const char vPfx[3];\ - const char vStr[40];}; - -// Macro to define the suffix to use when generating the extern version symbol. -// This is used by SysPlugin. We cannot use it here as cpp does not expand the -// macro when catenating tokens togther and we want to avoid yet another macro. -// -#define XrdVERSIONINFOSFX "_" - -// The following macro defines a local copy of version information. Parameters: -// x -> The variable name of the version information structure -// y -> An unquoted 1- to 15-character component name (e.g. cmsd, seckrb5) -// vn -> The integer version number to be used -// vs -> The string version number to be used -// -#define XrdVERSIONINFODEF(x,y,vn,vs) \ - XrdVersionInfo x = \ - {vn, (sizeof(#y)-1) & 0x0f,{'@','V',':'}, #y " " vs} - -// The following macro defines an externally referencable structure that records -// the version used to compile code. It is used by the plugin loader. Parms: -// x -> The variable name of the version information structure -// y -> An unquoted 1- to 15-character component name (e.g. cmsd, seckrb5, etc). -// -#define XrdVERSIONINFO(x,y) \ - extern "C" {XrdVERSIONINFODEF(x##_,y,XrdVNUMBER,XrdVERSION);} - -// The following macro is an easy way to declare externally defined version -// information. This macro must be used at file level. -// -#define XrdVERSIONINFOREF(x) extern "C" XrdVersionInfo x##_ - -// The following macro can be used to reference externally defined version -// information. As the composition of the symbolic name may change you should -// use this macro to refer to the version information declaration. -// -#define XrdVERSIONINFOVAR(x) x##_ -#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b982b387e24..b709a9b4a5c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,7 @@ endif() include(GoogleTest) add_subdirectory( XrdCl ) +add_subdirectory( XrdCeph ) add_subdirectory(XrdHttpTests) add_subdirectory( common ) diff --git a/src/XrdCeph/tests/CMakeLists.txt b/tests/XrdCeph/CMakeLists.txt similarity index 79% rename from src/XrdCeph/tests/CMakeLists.txt rename to tests/XrdCeph/CMakeLists.txt index 3b38c7dcd4e..19ce497b478 100644 --- a/src/XrdCeph/tests/CMakeLists.txt +++ b/tests/XrdCeph/CMakeLists.txt @@ -1,6 +1,6 @@ -find_package(GTest REQUIRED) - -include(GoogleTest) +if(NOT BUILD_CEPH) + return() +endif() add_executable(xrdceph-unit-tests XrdCeph.cc) diff --git a/src/XrdCeph/tests/XrdCeph.cc b/tests/XrdCeph/XrdCeph.cc similarity index 100% rename from src/XrdCeph/tests/XrdCeph.cc rename to tests/XrdCeph/XrdCeph.cc diff --git a/xrootd.spec b/xrootd.spec index 415e53a1956..b07567e2707 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -457,6 +457,7 @@ make -C %{_builddir}/%{name}-%{compat_version}/build %{?_smp_mflags} -DFORCE_ENABLED:BOOL=TRUE \ -DUSE_SYSTEM_ISAL:BOOL=TRUE \ -DENABLE_ASAN:BOOL=%{with asan} \ + -DENABLE_CEPH:BOOL=%{with ceph} \ -DENABLE_FUSE:BOOL=TRUE \ -DENABLE_KRB5:BOOL=TRUE \ -DENABLE_MACAROONS:BOOL=TRUE \ @@ -467,7 +468,6 @@ make -C %{_builddir}/%{name}-%{compat_version}/build %{?_smp_mflags} -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 \ From 5566e2e52aff10d533307ec7e03e8d9e4fd77ef8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 11:24:24 +0100 Subject: [PATCH 665/773] [Tests] Check only once if server tests can be enabled --- tests/CMakeLists.txt | 7 +++++++ tests/XRootD/CMakeLists.txt | 10 ---------- tests/cluster/CMakeLists.txt | 11 ----------- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b709a9b4a5c..a4c8a8f5c29 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,5 +16,12 @@ if( BUILD_XRDEC ) add_subdirectory( XrdEc ) # new tests with GTest endif() +execute_process(COMMAND id -u OUTPUT_VARIABLE UID + OUTPUT_STRIP_TRAILING_WHITESPACE) + +if(XRDCL_ONLY OR UID EQUAL 0) + return() +endif() + add_subdirectory( XRootD ) add_subdirectory( cluster ) diff --git a/tests/XRootD/CMakeLists.txt b/tests/XRootD/CMakeLists.txt index 7ac633ba807..d8207c4cae5 100644 --- a/tests/XRootD/CMakeLists.txt +++ b/tests/XRootD/CMakeLists.txt @@ -1,13 +1,3 @@ -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 "11940" CACHE STRING "Port for XRootD Test Server") list(APPEND XRDENV "XRDCP=$") diff --git a/tests/cluster/CMakeLists.txt b/tests/cluster/CMakeLists.txt index 2f59c9fb55e..e9a8f431973 100644 --- a/tests/cluster/CMakeLists.txt +++ b/tests/cluster/CMakeLists.txt @@ -1,14 +1,3 @@ -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() - list(APPEND XRDENV "XRDCP=$") list(APPEND XRDENV "XRDFS=$") list(APPEND XRDENV "CRC32C=$") From 4797d95768187005ba2f874def994f77387a721e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 11:31:10 +0100 Subject: [PATCH 666/773] [Tests] Remove CppUnit version of XrdEc tests --- tests/CMakeLists.txt | 18 +- tests/XrdEcTests/CMakeLists.txt | 42 -- tests/XrdEcTests/MicroTest.cc | 716 -------------------------------- 3 files changed, 10 insertions(+), 766 deletions(-) delete mode 100644 tests/XrdEcTests/CMakeLists.txt delete mode 100644 tests/XrdEcTests/MicroTest.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a4c8a8f5c29..48fe5cda317 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,19 +3,21 @@ if(NOT BUILD_TESTS) endif() include(GoogleTest) -add_subdirectory( XrdCl ) -add_subdirectory( XrdCeph ) + +add_subdirectory(common) + +add_subdirectory(XrdCl) +add_subdirectory(XrdCeph) + +if(BUILD_XRDEC) +add_subdirectory(XrdEc) +endif() + add_subdirectory(XrdHttpTests) -add_subdirectory( common ) add_subdirectory( XrdClTests ) add_subdirectory( XrdSsiTests ) -if( BUILD_XRDEC ) - add_subdirectory( XrdEcTests ) - add_subdirectory( XrdEc ) # new tests with GTest -endif() - execute_process(COMMAND id -u OUTPUT_VARIABLE UID OUTPUT_STRIP_TRAILING_WHITESPACE) diff --git a/tests/XrdEcTests/CMakeLists.txt b/tests/XrdEcTests/CMakeLists.txt deleted file mode 100644 index e656ab4b585..00000000000 --- a/tests/XrdEcTests/CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ - -add_library( - XrdEcTests MODULE - MicroTest.cc -) - -target_link_libraries( - XrdEcTests - PRIVATE - XrdEc - XrdCl - XrdUtils - ${ISAL_LIBRARIES} - ${CPPUNIT_LIBRARIES}) - -target_include_directories(XrdEcTests PRIVATE ../common ${CPPUNIT_INCLUDE_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 -#------------------------------------------------------------------------------- -install( - TARGETS XrdEcTests - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/tests/XrdEcTests/MicroTest.cc b/tests/XrdEcTests/MicroTest.cc deleted file mode 100644 index 5b375c6fe4f..00000000000 --- a/tests/XrdEcTests/MicroTest.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 -#include "TestEnv.hh" -#include "CppUnitXrdHelpers.hh" - -#include "XrdEc/XrdEcStrmWriter.hh" -#include "XrdEc/XrdEcReader.hh" -#include "XrdEc/XrdEcObjCfg.hh" - -#include "XrdCl/XrdClMessageUtils.hh" - -#include "XrdZip/XrdZipCDFH.hh" - -#include "XrdSys/XrdSysPlatform.hh" - -#include -#include -#include - -#include -#include -#include -#include -#include - -using namespace XrdEc; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class MicroTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( MicroTest ); - CPPUNIT_TEST( AlignedWriteTest ); - CPPUNIT_TEST( SmallWriteTest ); - CPPUNIT_TEST( BigWriteTest ); - CPPUNIT_TEST( VectorReadTest ); - CPPUNIT_TEST( IllegalVectorReadTest ); - CPPUNIT_TEST( AlignedWrite1MissingTest ); - CPPUNIT_TEST( AlignedWrite2MissingTest ); - CPPUNIT_TEST( AlignedWriteTestIsalCrcNoMt ); - CPPUNIT_TEST( SmallWriteTestIsalCrcNoMt ); - CPPUNIT_TEST( BigWriteTestIsalCrcNoMt ); - CPPUNIT_TEST( AlignedWrite1MissingTestIsalCrcNoMt ); - CPPUNIT_TEST( AlignedWrite2MissingTestIsalCrcNoMt ); - CPPUNIT_TEST_SUITE_END(); - - 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; -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( MicroTest ); - - -void MicroTest::Init( bool usecrc32c ) -{ - objcfg.reset( new ObjCfg( "test.txt", nbdata, nbparity, chsize, usecrc32c, true ) ); - rawdata.clear(); - - char tmpdir[MAXPATHLEN]; - CPPUNIT_ASSERT( getcwd(tmpdir, MAXPATHLEN - 21) ); - strcat(tmpdir, "/xrootd-xrdec-XXXXXX"); - // create the data directory - 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 ) - { - std::stringstream ss; - ss << std::setfill('0') << std::setw( 2 ) << i; - std::string strp = datadir + '/' + ss.str() + '/'; - objcfg->plgr.emplace_back( strp ); - CPPUNIT_ASSERT( mkdir( strp.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) == 0 ); - } -} - -void MicroTest::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(); - CPPUNIT_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(); - CPPUNIT_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 ); - CPPUNIT_ASSERT_XRDST( status2 ); - std::string str = "XXXXXXXX"; - status2 = f.Write( offset, str.size(), str.c_str() ); - CPPUNIT_ASSERT_XRDST( status2 ); - status2 = f.Close(); - CPPUNIT_ASSERT_XRDST( status2 ); -} - -void MicroTest::UrlNotReachable( size_t index ) -{ - XrdCl::URL url( objcfg->plgr[index] ); - CPPUNIT_ASSERT( chmod( url.GetPath().c_str(), 0 ) == 0 ); -} - -void MicroTest::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; - CPPUNIT_ASSERT( chmod( url.GetPath().c_str(), mode ) == 0 ); -} - -void MicroTest::CorruptedReadVerify() -{ - UrlNotReachable( 0 ); - ReadVerifyAll(); - UrlNotReachable( 1 ); - ReadVerifyAll(); - UrlReachable( 0 ); - UrlReachable( 1 ); - - CorruptChunk( 0, 1 ); - ReadVerifyAll(); - - CorruptChunk( 0, 2 ); - ReadVerifyAll(); - -} - -void MicroTest::VerifyVectorRead(uint32_t seed){ - Reader reader( *objcfg ); - // open the data object - XrdCl::SyncResponseHandler handler1; - reader.Open( &handler1 ); - handler1.WaitForResponse(); - XrdCl::XRootDStatus *status = handler1.GetStatus(); - CPPUNIT_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(); - CPPUNIT_ASSERT_XRDST( *status ); - delete status; - for(int i = 0; i < 5; i++){ - std::string result(buffers[i].data(), expected[i].size()); - CPPUNIT_ASSERT( result == expected[i] ); - } - - XrdCl::SyncResponseHandler handler2; - reader.Close( &handler2 ); - handler2.WaitForResponse(); - status = handler2.GetStatus(); - CPPUNIT_ASSERT_XRDST( *status ); - delete status; -} - -void MicroTest::IllegalVectorRead(uint32_t seed){ - Reader reader(*objcfg); - // open the data object - XrdCl::SyncResponseHandler handler1; - reader.Open(&handler1); - handler1.WaitForResponse(); - XrdCl::XRootDStatus *status = handler1.GetStatus(); - CPPUNIT_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()) - { - CPPUNIT_ASSERT(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()) - { - CPPUNIT_ASSERT(false); - } - delete status; - - XrdCl::SyncResponseHandler handler2; - reader.Close(&handler2); - handler2.WaitForResponse(); - status = handler2.GetStatus(); - CPPUNIT_ASSERT_XRDST(*status); - delete status; -} - -void MicroTest::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(); - CPPUNIT_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(); - CPPUNIT_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 - CPPUNIT_ASSERT( 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(); - CPPUNIT_ASSERT_XRDST( *status ); - delete status; -} - -void MicroTest::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(); - CPPUNIT_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(); - CPPUNIT_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 - CPPUNIT_ASSERT( result == expected ); - delete status; - delete rsp; - delete[] rdbuff; - - // close the data object - XrdCl::SyncResponseHandler handler2; - reader.Close( &handler2 ); - handler2.WaitForResponse(); - status = handler2.GetStatus(); - CPPUNIT_ASSERT_XRDST( *status ); - delete status; -} - -void MicroTest::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(); - CPPUNIT_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(); - CPPUNIT_ASSERT( 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(); - CPPUNIT_ASSERT_XRDST( *status ); - delete status; -} - -int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) -{ - int rc = remove( fpath ); - CPPUNIT_ASSERT( rc == 0 ); - return rc; -} - -void MicroTest::CleanUp() -{ - // delete the data directory - nftw( datadir.c_str(), unlink_cb, 64, FTW_DEPTH | FTW_PHYS ); -} - -void MicroTest::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(); - CPPUNIT_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(); - CPPUNIT_ASSERT_XRDST( *status ); - delete status; -} - -void MicroTest::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(); - CPPUNIT_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(); - CPPUNIT_ASSERT_XRDST( *status ); - delete status; - - // verify that we wrote the data correctly - Verify(); - // clean up the data directory - CleanUp(); -} - From 2ee9c9dc477b2a7bbe077e1f39ed38f931967e71 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 11:43:19 +0100 Subject: [PATCH 667/773] [Tests] Remove CppUnit version of XrdCl tests --- tests/CMakeLists.txt | 1 - tests/XrdClTests/CMakeLists.txt | 71 -- tests/XrdClTests/FileCopyTest.cc | 651 -------------- tests/XrdClTests/FileSystemTest.cc | 689 -------------- tests/XrdClTests/FileTest.cc | 811 ----------------- tests/XrdClTests/IdentityPlugIn.cc | 488 ---------- tests/XrdClTests/IdentityPlugIn.hh | 55 -- tests/XrdClTests/LocalFileHandlerTest.cc | 550 ------------ tests/XrdClTests/MonitorTestLib.cc | 213 ----- tests/XrdClTests/OperationsWorkflowTest.cc | 991 --------------------- tests/XrdClTests/PollerTest.cc | 280 ------ tests/XrdClTests/PostMasterTest.cc | 581 ------------ tests/XrdClTests/SocketTest.cc | 307 ------- tests/XrdClTests/ThreadingTest.cc | 348 -------- tests/XrdClTests/UtilsTest.cc | 264 ------ tests/XrdClTests/XRootDProtocolHelper.cc | 118 --- tests/XrdClTests/XRootDProtocolHelper.hh | 45 - tests/XrdClTests/cppunit.supp | 17 - tests/XrdClTests/printenv.sh | 24 - tests/XrdClTests/tls/CMakeLists.txt | 32 - tests/XrdClTests/tls/README.md | 19 - tests/XrdClTests/tls/xrdcl-tls.cc | 70 -- tests/XrdClTests/tls/xrdsrv-tls.cc | 698 --------------- tests/XrdClTests/wrt/xrdsrv-dio.cc | 602 ------------- tests/XrdClTests/wrt/xrdsrv-wrt.cc | 563 ------------ 25 files changed, 8488 deletions(-) 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/OperationsWorkflowTest.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/XrdClTests/tls/CMakeLists.txt delete mode 100644 tests/XrdClTests/tls/README.md delete mode 100644 tests/XrdClTests/tls/xrdcl-tls.cc delete mode 100644 tests/XrdClTests/tls/xrdsrv-tls.cc delete mode 100644 tests/XrdClTests/wrt/xrdsrv-dio.cc delete mode 100644 tests/XrdClTests/wrt/xrdsrv-wrt.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 48fe5cda317..84f3a653f12 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,7 +15,6 @@ endif() add_subdirectory(XrdHttpTests) -add_subdirectory( XrdClTests ) add_subdirectory( XrdSsiTests ) execute_process(COMMAND id -u OUTPUT_VARIABLE UID diff --git a/tests/XrdClTests/CMakeLists.txt b/tests/XrdClTests/CMakeLists.txt deleted file mode 100644 index 238e2509899..00000000000 --- a/tests/XrdClTests/CMakeLists.txt +++ /dev/null @@ -1,71 +0,0 @@ - -add_subdirectory( tls ) - -set( LIB_XRD_CL_TEST_MONITOR XrdClTestMonitor-${PLUGIN_VERSION} ) - - -if( XrdClPipelines ) - set( OperationsWorkflowTest OperationsWorkflowTest.cc ) -endif() - -add_library( - XrdClTests MODULE - UtilsTest.cc - SocketTest.cc - PollerTest.cc - PostMasterTest.cc - FileSystemTest.cc - FileTest.cc - FileCopyTest.cc - ThreadingTest.cc - IdentityPlugIn.cc - LocalFileHandlerTest.cc - - ${OperationsWorkflowTest} -) - -target_link_libraries( - XrdClTests - XrdClTestsHelper - ${CMAKE_THREAD_LIBS_INIT} - ${CPPUNIT_LIBRARIES} - ZLIB::ZLIB - XrdCl ) - -target_include_directories( XrdClTests PRIVATE ../common ${CPPUNIT_INCLUDE_DIRS} ) - -add_library( - ${LIB_XRD_CL_TEST_MONITOR} MODULE - MonitorTestLib.cc -) - -target_link_libraries( - ${LIB_XRD_CL_TEST_MONITOR} - XrdClTestsHelper - XrdCl ) - -target_include_directories( ${LIB_XRD_CL_TEST_MONITOR} PRIVATE ../common ) - -foreach(TEST_SUITE - # File - # FileCopy - # FileSystem - # LocalFileHandler - Poller - # PostMaster - Socket - # Threading - Utils - # Workflow -) - add_test(NAME XrdCl::${TEST_SUITE} - COMMAND $ $ "All Tests/${TEST_SUITE}Test") - set_tests_properties(XrdCl::${TEST_SUITE} PROPERTIES RUN_SERIAL TRUE) -endforeach() - -#------------------------------------------------------------------------------- -# 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 90e6845e795..00000000000 --- a/tests/XrdClTests/FileCopyTest.cc +++ /dev/null @@ -1,651 +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 lastUrl; - CPPUNIT_ASSERT( f.GetProperty( "LastURL", lastUrl ) ); - CPPUNIT_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", - URL( lastUrl ) ) ); - 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::Update ) ); - - //---------------------------------------------------------------------------- - // 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, lastUrl; - f.GetProperty( "LastURL", lastUrl ); - CPPUNIT_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", - lastUrl ) ); - 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 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 ); - 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 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 ); - CPPUNIT_ASSERT_XRDST( process10.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process10.Prepare() ); - CPPUNIT_ASSERT_XRDST( process10.Run(0) ); - CPPUNIT_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 ); - CPPUNIT_ASSERT_XRDST( process12.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process12.Prepare() ); - 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 && 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 ); - CPPUNIT_ASSERT_XRDST( process13.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process13.Prepare() ); - CPPUNIT_ASSERT_XRDST_NOTOK( process13.Run(0), XrdCl::errCheckSumError ); - env->PutInt( "ZipMtlnCksum", 0 ); - CPPUNIT_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 - CPPUNIT_ASSERT_XRDST( process14.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process14.Prepare() ); - CPPUNIT_ASSERT_XRDST( process14.Run(0) ); - CPPUNIT_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 - CPPUNIT_ASSERT_XRDST( process15.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process15.Prepare() ); - CPPUNIT_ASSERT_XRDST_NOTOK( process15.Run(0), XrdCl::errOperationExpired ); - CPPUNIT_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 - CPPUNIT_ASSERT_XRDST( process16.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process16.Prepare() ); - CPPUNIT_ASSERT_XRDST_NOTOK( process16.Run( &progress16 ), errOperationInterrupted ); - XrdCl::FileSystem localfs( "file://localhost" ); - XrdCl::StatInfo *ptr = nullptr; - CPPUNIT_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" ); - CPPUNIT_ASSERT_XRDST( process17.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process17.Prepare() ); - CPPUNIT_ASSERT_XRDST( process17.Run(0) ); - CPPUNIT_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" ); - 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(); - - //---------------------------------------------------------------------------- - // 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 with extended attributes - //---------------------------------------------------------------------------- - - // set extended attributes in the local source file - File lf; - CPPUNIT_ASSERT_XRDST( lf.Open( "file://localhost" + localFile, OpenFlags::Write ) ); - std::vector attrs; attrs.push_back( xattr_t( "foo", "bar" ) ); - std::vector result; - CPPUNIT_ASSERT_XRDST( lf.SetXAttr( attrs, result ) ); - CPPUNIT_ASSERT( result.size() == 1 ); - CPPUNIT_ASSERT_XRDST( result.front().status ); - CPPUNIT_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 ); - CPPUNIT_ASSERT_XRDST( process9.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process9.Prepare() ); - CPPUNIT_ASSERT_XRDST( process9.Run(0) ); - properties.Clear(); - - // now test if the xattrs were preserved - std::vector xattrs; - CPPUNIT_ASSERT_XRDST( fs.ListXAttr( targetPath, xattrs ) ); - CPPUNIT_ASSERT( xattrs.size() == 1 ); - XAttr &xattr = xattrs.front(); - CPPUNIT_ASSERT_XRDST( xattr.status ); - CPPUNIT_ASSERT( xattr.name == "foo" && xattr.value == "bar" ); - - //---------------------------------------------------------------------------- - // Cleanup - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( fs.Rm( targetPath ) ); - CPPUNIT_ASSERT( 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" ); - CPPUNIT_ASSERT_XRDST( process1.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process1.Prepare() ); - CPPUNIT_ASSERT_XRDST( process1.Run(0) ); - CPPUNIT_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" ); - CPPUNIT_ASSERT_XRDST( process11.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process11.Prepare() ); - CPPUNIT_ASSERT_XRDST( process11.Run(0) ); - CPPUNIT_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; -// 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( "target", targetURL ); - properties.Set( "initTimeout", 10 ); - properties.Set( "thirdParty", "only" ); - CPPUNIT_ASSERT_XRDST( process3.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process3.Prepare() ); - XrdCl::XRootDStatus status = process3.Run(0); - CPPUNIT_ASSERT( !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:9999//test" ); - properties.Set( "initTimeout", 10 ); - properties.Set( "thirdParty", "only" ); - CPPUNIT_ASSERT_XRDST( process4.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process4.Prepare() ); - status = process4.Run(0); - CPPUNIT_ASSERT( !status.IsOK() && ( status.code == errOperationExpired || status.code == errConnectionError ) ); -} - -//------------------------------------------------------------------------------ -// 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 80645a27e2f..00000000000 --- a/tests/XrdClTests/FileSystemTest.cc +++ /dev/null @@ -1,689 +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( XAttrTest ); - 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 XAttrTest(); - 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 ); - - 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; - CPPUNIT_ASSERT_XRDST( fs.DeepLocate( lsPath, OpenFlags::PrefName, info ) ); - CPPUNIT_ASSERT( info ); - - for( auto itr = info->Begin(); itr != info->End(); ++itr ) - { - XrdSysSemaphore sem( 0 ); - auto handler = XrdCl::ResponseHandler::Wrap( [&]( auto &s, auto &r ) - { - CPPUNIT_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() ) ); - CPPUNIT_ASSERT_XRDST( fs1.DirList( lsPath, DirListFlags::Stat | DirListFlags::Chunked, handler ) ); - sem.Wait(); - } - delete info; - info = 0; - - CPPUNIT_ASSERT( dirls1 == dirls2 ); - - //---------------------------------------------------------------------------- - // Now list an empty directory - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( fs.MkDir( "/data/empty", MkDirFlags::None, Access::None ) ); - CPPUNIT_ASSERT_XRDST( fs.DeepLocate( "/data/empty", OpenFlags::PrefName, info ) ); - CPPUNIT_ASSERT( info->GetSize() ); - FileSystem fs3( info->Begin()->GetAddress() ); - CPPUNIT_ASSERT_XRDST( fs3.DirList( "/data/empty", DirListFlags::Stat, list ) ); - CPPUNIT_ASSERT( list ); - CPPUNIT_ASSERT( list->GetSize() == 0 ); - CPPUNIT_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; - - 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; -} - -//------------------------------------------------------------------------------ -// Extended attributes test -//------------------------------------------------------------------------------ -void FileSystemTest::XAttrTest() -{ - 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 ); - - 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; - CPPUNIT_ASSERT_XRDST( fs.SetXAttr( remoteFile, attrs, result1 ) ); - - auto itr2 = result1.begin(); - for( ; itr2 != result1.end() ; ++itr2 ) - CPPUNIT_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; - CPPUNIT_ASSERT_XRDST( fs.GetXAttr( remoteFile, names, result2 ) ); - - auto itr3 = result2.begin(); - for( ; itr3 != result2.end() ; ++itr3 ) - { - CPPUNIT_ASSERT_XRDST( itr3->status ); - auto match = attributes.find( itr3->name ); - CPPUNIT_ASSERT( match != attributes.end() ); - CPPUNIT_ASSERT( match->second == itr3->value ); - } - result2.clear(); - - //---------------------------------------------------------------------------- - // Test ListXAttr - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( fs.ListXAttr( remoteFile, result2 ) ); - - itr3 = result2.begin(); - for( ; itr3 != result2.end() ; ++itr3 ) - { - CPPUNIT_ASSERT_XRDST( itr3->status ); - auto match = attributes.find( itr3->name ); - CPPUNIT_ASSERT( match != attributes.end() ); - CPPUNIT_ASSERT( match->second == itr3->value ); - } - - result2.clear(); - - //---------------------------------------------------------------------------- - // Test DelXAttr - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( fs.DelXAttr( remoteFile, names, result1 ) ); - - itr2 = result1.begin(); - for( ; itr2 != result1.end() ; ++itr2 ) - CPPUNIT_ASSERT_XRDST( itr2->status ); - - result1.clear(); -} - -//------------------------------------------------------------------------------ -// 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 60eaf9fdda8..00000000000 --- a/tests/XrdClTests/FileTest.cc +++ /dev/null @@ -1,811 +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/XrdClZipArchive.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClZipOperations.hh" - -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( XAttrTest ); - CPPUNIT_TEST( PlugInTest ); - CPPUNIT_TEST_SUITE_END(); - void RedirectReturnTest(); - void ReadTest(); - void WriteTest(); - void WriteVTest(); - void VectorReadTest(); - void VectorWriteTest(); - void VirtualRedirectorTest(); - void XAttrTest(); - 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; - - //---------------------------------------------------------------------------- - // 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 = XrdClTests::Utils::ComputeCRC32( buffer1, 40*MB ); - CPPUNIT_ASSERT( crc == 3303853367UL ); - - crc = XrdClTests::Utils::ComputeCRC32( buffer2, 40000000 ); - CPPUNIT_ASSERT( crc == 898701504UL ); - - delete [] buffer1; - delete [] buffer2; - - CPPUNIT_ASSERT_XRDST( f.Close() ); - - //---------------------------------------------------------------------------- - // Read ZIP archive test (uncompressed) - //---------------------------------------------------------------------------- - std::string archiveUrl = address + "/" + dataPath + "/data.zip"; - - ZipArchive zip; - CPPUNIT_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; - CPPUNIT_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 ); - } - ) ); - CPPUNIT_ASSERT( testset[i].expected == result ); - } - - CPPUNIT_ASSERT_XRDST( WaitFor( CloseArchive( zip ) ) ); -} - - -//------------------------------------------------------------------------------ -// 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( XrdClTests::Utils::GetRandomBytes( buffer1, 4*MB ) == 4*MB ); - CPPUNIT_ASSERT( 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 - //---------------------------------------------------------------------------- - 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 = XrdClTests::Utils::ComputeCRC32( buffer3, 4*MB ); - crc2 = XrdClTests::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 response; - 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( XrdClTests::Utils::GetRandomBytes( buffer1, 4*MB ) == 4*MB ); - CPPUNIT_ASSERT( 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 - //---------------------------------------------------------------------------- - 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 = XrdClTests::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 = XrdClTests::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 = XrdClTests::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 = 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; - 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 = XrdClTests::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 ) ); - URL lastUrl( value ); - CPPUNIT_ASSERT( lastUrl.GetLocation() == 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 ) ); - URL lastUrl2( value ); - CPPUNIT_ASSERT( lastUrl2.GetLocation() == 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 ) ); - URL lastUrl3( value ); - CPPUNIT_ASSERT( lastUrl3.GetLocation() == 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 ) ); - URL lastUrl4( value ); - CPPUNIT_ASSERT( lastUrl4.GetLocation() == 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) ); -} - -void FileTest::XAttrTest() -{ - 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 ) ); - - std::string filePath = dataPath + "/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; - std::string fileUrl = address + "/" + filePath; - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - - File file; - CPPUNIT_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; - CPPUNIT_ASSERT_XRDST( file.SetXAttr( attrs, result1 ) ); - - auto itr2 = result1.begin(); - for( ; itr2 != result1.end() ; ++itr2 ) - CPPUNIT_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; - CPPUNIT_ASSERT_XRDST( file.GetXAttr( names, result2 ) ); - - auto itr3 = result2.begin(); - for( ; itr3 != result2.end() ; ++itr3 ) - { - CPPUNIT_ASSERT_XRDST( itr3->status ); - auto match = attributes.find( itr3->name ); - CPPUNIT_ASSERT( match != attributes.end() ); - CPPUNIT_ASSERT( match->second == itr3->value ); - } - - //---------------------------------------------------------------------------- - // Test ListXAttr - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( file.ListXAttr( result2 ) ); - - itr3 = result2.begin(); - for( ; itr3 != result2.end() ; ++itr3 ) - { - CPPUNIT_ASSERT_XRDST( itr3->status ); - auto match = attributes.find( itr3->name ); - CPPUNIT_ASSERT( match != attributes.end() ); - CPPUNIT_ASSERT( match->second == itr3->value ); - } - - //---------------------------------------------------------------------------- - // Test DelXAttr - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( file.DelXAttr( names, result1 ) ); - - itr2 = result1.begin(); - for( ; itr2 != result1.end() ; ++itr2 ) - CPPUNIT_ASSERT_XRDST( itr2->status ); - - CPPUNIT_ASSERT_XRDST( file.Close() ); -} - -//------------------------------------------------------------------------------ -// 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 f8618c14f7b..00000000000 --- a/tests/XrdClTests/LocalFileHandlerTest.cc +++ /dev/null @@ -1,550 +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( XAttrTest ); - 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(); - void XAttrTest(); -}; -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(), O_RDWR ); - 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(), O_RDWR ); - 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(), O_RDWR ); - 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; - - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); -} - -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 ); -} - -void 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; - CPPUNIT_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; - - CPPUNIT_ASSERT_XRDST( f.SetXAttr( attrs, st_resp ) ); - - std::vector::iterator itr1; - for( itr1 = st_resp.begin(); itr1 != st_resp.end(); ++itr1 ) - CPPUNIT_ASSERT_XRDST( itr1->status ); - - //---------------------------------------------------------------------------- - // Test XAttr Get - //---------------------------------------------------------------------------- - std::vector names; - names.push_back( "version" ); - names.push_back( "description" ); - std::vector resp; - CPPUNIT_ASSERT_XRDST( f.GetXAttr( names, resp ) ); - - CPPUNIT_ASSERT_XRDST( resp[0].status ); - CPPUNIT_ASSERT_XRDST( resp[1].status ); - - CPPUNIT_ASSERT( resp.size() == 2 ); - int vid = resp[0].name == "version" ? 0 : 1; - int did = vid == 0 ? 1 : 0; - CPPUNIT_ASSERT( resp[vid].name == "version" && - resp[vid].value == "v3.3.3" ); - CPPUNIT_ASSERT( resp[did].name == "description" && - resp[did].value == "a very important file" ); - - //---------------------------------------------------------------------------- - // Test XAttr Del - //---------------------------------------------------------------------------- - names.clear(); - names.push_back( "description" ); - st_resp.clear(); - CPPUNIT_ASSERT_XRDST( f.DelXAttr( names, st_resp ) ); - CPPUNIT_ASSERT( st_resp.size() == 1 ); - CPPUNIT_ASSERT_XRDST( st_resp[0].status ); - - //---------------------------------------------------------------------------- - // Test XAttr List - //---------------------------------------------------------------------------- - resp.clear(); - CPPUNIT_ASSERT_XRDST( f.ListXAttr( resp ) ); - CPPUNIT_ASSERT( resp.size() == 2 ); - vid = resp[0].name == "version" ? 0 : 1; - int cid = vid == 0 ? 1 : 0; - CPPUNIT_ASSERT( resp[vid].name == "version" && - resp[vid].value == "v3.3.3" ); - CPPUNIT_ASSERT( resp[cid].name == "checksum" && - resp[cid].value == "0x22334455" ); - - //---------------------------------------------------------------------------- - // Cleanup - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.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 d5451084213..00000000000 --- a/tests/XrdClTests/MonitorTestLib.cc +++ /dev/null @@ -1,213 +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, writev: %ld", i->file->GetURL().c_str(), i->rBytes, i->vrBytes, - i->wBytes, i->vwBytes ); - 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::ErrWriteV: op = "WriteV"; 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/OperationsWorkflowTest.cc b/tests/XrdClTests/OperationsWorkflowTest.cc deleted file mode 100644 index 6f919d67af1..00000000000 --- a/tests/XrdClTests/OperationsWorkflowTest.cc +++ /dev/null @@ -1,991 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Krzysztof Jamrog -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that 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 "TestEnv.hh" -#include "IdentityPlugIn.hh" -#include "CppUnitXrdHelpers.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 CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( WorkflowTest ); - CPPUNIT_TEST( ReadingWorkflowTest ); - CPPUNIT_TEST( WritingWorkflowTest ); - CPPUNIT_TEST( MissingParameterTest ); - CPPUNIT_TEST( OperationFailureTest ); - CPPUNIT_TEST( DoubleRunningTest ); - CPPUNIT_TEST( ParallelTest ); - CPPUNIT_TEST( FileSystemWorkflowTest ); - CPPUNIT_TEST( MixedWorkflowTest ); - CPPUNIT_TEST( WorkflowWithFutureTest ); - CPPUNIT_TEST( XAttrWorkflowTest ); - CPPUNIT_TEST( MkDirAsyncTest ); - CPPUNIT_TEST( CheckpointTest ); - CPPUNIT_TEST_SUITE_END(); - void ReadingWorkflowTest(); - void WritingWorkflowTest(); - void MissingParameterTest(); - void OperationFailureTest(); - void DoubleRunningTest(); - void ParallelTest(); - void FileSystemWorkflowTest(); - void MixedWorkflowTest(); - void WorkflowWithFutureTest(); - void XAttrWorkflowTest(); - void MkDirAsyncTest(); - void CheckpointTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( WorkflowTest ); - - -namespace { - using namespace XrdCl; - - XrdCl::URL GetAddress(){ - Env *testEnv = TestEnv::GetEnv(); - std::string address; - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - return XrdCl::URL(address); - } - - std::string GetPath(const std::string &fileName){ - Env *testEnv = TestEnv::GetEnv(); - - std::string dataPath; - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - return dataPath + "/" + fileName; - } - - - std::string GetFileUrl(const std::string &fileName){ - 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 + "/" + 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) { - CPPUNIT_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) { - CPPUNIT_ASSERT( !status->IsOK() ); - delete status; - delete response; - executed = true; - } - - bool Executed(){ - return executed; - } - - protected: - bool executed; - }; - - - -} - - -void 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 - { - CPPUNIT_ASSERT_XRDST( status ); - CPPUNIT_ASSERT( 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 ); - CPPUNIT_ASSERT_XRDST( status ); - - CPPUNIT_ASSERT( openHandler.Executed() ); - CPPUNIT_ASSERT( readHandler.Executed() ); - CPPUNIT_ASSERT( closeHandler.Executed() ); - - delete[] reinterpret_cast( *buffer ); -} - - -void 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 ) - { - CPPUNIT_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 - { - CPPUNIT_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 - { - CPPUNIT_ASSERT_XRDST( status ); - CPPUNIT_ASSERT( 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 ) ); - CPPUNIT_ASSERT_XRDST( status ); - CPPUNIT_ASSERT( rdresp.get() == texts[0] + texts[1] + texts[2] ); - - free( (*iov)[0].iov_base ); - free( (*iov)[1].iov_base ); - free( (*iov)[2].iov_base ); -} - - -void 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 ) ); - CPPUNIT_ASSERT( status.IsError() ); - //---------------------------------------------------------------------------- - // If there is an error, last handlers should not be executed - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( readHandler.Executed() ); - CPPUNIT_ASSERT( !closed & error ); -} - - - -void 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 - CPPUNIT_ASSERT( status.IsError() ); - - //---------------------------------------------------------------------------- - // If there is an error, handlers should not be executed - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT(openHandler.Executed()); - - try - { - statresp.get(); - } - catch( PipelineException &ex ) - { - CPPUNIT_ASSERT_XRDST_NOTOK( ex.GetError(), errPipelineFailed ); - } - - try - { - readresp.get(); - } - catch( PipelineException &ex ) - { - CPPUNIT_ASSERT_XRDST_NOTOK( ex.GetError(), errPipelineFailed ); - } - - try - { - closeresp.get(); - } - catch( PipelineException &ex ) - { - CPPUNIT_ASSERT_XRDST_NOTOK( ex.GetError(), errPipelineFailed ); - } -} - - -void 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 ); - CPPUNIT_ASSERT( false ); - } - catch( std::logic_error &err ) - { - - } - - - XRootDStatus status = ftr.get(); - - //---------------------------------------------------------------------------- - // Running workflow again should fail - //---------------------------------------------------------------------------- - try - { - Async( pipe ); - CPPUNIT_ASSERT( false ); - } - catch( std::logic_error &err ) - { - - } - - CPPUNIT_ASSERT( status.IsOK() ); - - CPPUNIT_ASSERT( opened ); - CPPUNIT_ASSERT( closed ); -} - - -void 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 ) ); - CPPUNIT_ASSERT_XRDST( WaitFor( Parallel( pipes ) >> []( XRootDStatus &status ){ CPPUNIT_ASSERT_XRDST( status ); } ) ); - CPPUNIT_ASSERT( 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 ) ); - CPPUNIT_ASSERT(status.IsOK()); - - CPPUNIT_ASSERT(lockHandlerExecuted); - - try - { - parallelresp.get(); - closeresp.get(); - } - catch( std::exception &ex ) - { - CPPUNIT_ASSERT( 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 ){ CPPUNIT_ASSERT_XRDST( status ); exec1 = true; }, - Rm( fs, dataRelativePath ) >> [&]( XRootDStatus &status ){ CPPUNIT_ASSERT_XRDST( status ); exec2 = true; } ) ); - CPPUNIT_ASSERT_XRDST( WaitFor( std::move( deletingPipe ) ) ); - - CPPUNIT_ASSERT( exec1 ); - CPPUNIT_ASSERT( 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"; - CPPUNIT_ASSERT_XRDST( WaitFor( Parallel( Stat( fs, url_exists ), Stat( fs, url_exists ) ).Any() ) ); - - std::string also_exists = "/data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; - CPPUNIT_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; - }; - - CPPUNIT_ASSERT_XRDST( WaitFor( Parallel( Stat( fs, url_exists ) >> hndl, - Stat( fs, also_exists ) >> hndl, - Stat( fs, not_exists ) >> hndl ).AtLeast( 1 ) ) ); - CPPUNIT_ASSERT( okcnt == 2 && errcnt == 1 ); -} - - -void 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 ) ); - CPPUNIT_ASSERT(status.IsOK()); - - CPPUNIT_ASSERT(mkDirHandler.Executed()); - CPPUNIT_ASSERT(locateHandler.Executed()); - CPPUNIT_ASSERT(moveHandler.Executed()); - CPPUNIT_ASSERT(secondLocateHandler.Executed()); - CPPUNIT_ASSERT(removeHandler.Executed()); -} - - -void 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); - CPPUNIT_ASSERT(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 - { - CPPUNIT_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 ){ CPPUNIT_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 ) ); - CPPUNIT_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; - CPPUNIT_ASSERT( result == content[i] ); - } - - CPPUNIT_ASSERT(cleaningHandlerExecuted); -} - - -void WorkflowTest::WorkflowWithFutureTest() -{ - 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 *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 - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); - CPPUNIT_ASSERT_XRDST( f.Read( 10*MB, 40*MB, expected, bytesRead ) ); - CPPUNIT_ASSERT( bytesRead == 40*MB ); - CPPUNIT_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(); - CPPUNIT_ASSERT( result.length = bytesRead ); - CPPUNIT_ASSERT( strncmp( expected, (char*)result.buffer, bytesRead ) == 0 ); - } - catch( PipelineException &ex ) - { - CPPUNIT_ASSERT( false ); - } - - CPPUNIT_ASSERT_XRDST( status.get() ) - - delete[] expected; - delete[] buffer; -} - -void WorkflowTest::XAttrWorkflowTest() -{ - 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; - - //---------------------------------------------------------------------------- - // 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 ); - CPPUNIT_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 ); - - CPPUNIT_ASSERT_XRDST( WaitFor( std::move( get_del ) ) ); - - try - { - CPPUNIT_ASSERT( xattr_value == rsp1.get() ); - } - catch( PipelineException &ex ) - { - CPPUNIT_ASSERT( 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 ); - CPPUNIT_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 ) - { - CPPUNIT_ASSERT_XRDST( status ); - CPPUNIT_ASSERT( 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; } ); - CPPUNIT_ASSERT( itr != attrs.end() ); - CPPUNIT_ASSERT( std::get<1>( *itr ) == rsp[i].value ); - } - } - | DelXAttr( file4, names ) - | Close( file4 ); - - CPPUNIT_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 ) - { - CPPUNIT_ASSERT_XRDST( status ); - CPPUNIT_ASSERT( rsp.size() == 1 ); - CPPUNIT_ASSERT( rsp[0].name == xattr_name ); - CPPUNIT_ASSERT( rsp[0].value == xattr_value ); - } - | DelXAttr( fs, filePath, xattr_name ); - - CPPUNIT_ASSERT_XRDST( WaitFor( std::move( pipeline ) ) ); - - try - { - CPPUNIT_ASSERT( xattr_value == rsp2.get() ); - } - catch( PipelineException &ex ) - { - CPPUNIT_ASSERT( false ); - } -} - -void 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" ) - ); - - CPPUNIT_ASSERT(t.get().status == stOK); -} - -void 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"; - - CPPUNIT_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"; - - CPPUNIT_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) - CPPUNIT_ASSERT_XRDST( WaitFor( Open( f3, url, OpenFlags::Read ) | - Read( f3, 0, sizeof( readout ), readout ) | - Close( f3 ) ) ); - // we expect the data to be unchanged - CPPUNIT_ASSERT( strncmp( readout, data, sizeof( data ) ) == 0 ); - - //--------------------------------------------------------------------------- - // Update the file and commit the changes - //--------------------------------------------------------------------------- - File f4; - CPPUNIT_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) - CPPUNIT_ASSERT_XRDST( WaitFor( Open( f5, url, OpenFlags::Read ) | - Read( f5, 0, sizeof( readout ), readout ) | - Close( f5 ) ) ); - // we expect the data to be unchanged - CPPUNIT_ASSERT( strncmp( readout, update, sizeof( update ) ) == 0 ); - CPPUNIT_ASSERT( strncmp( readout + sizeof( update ), data + sizeof( update ), - sizeof( data ) - sizeof( update ) ) == 0 ); - - //--------------------------------------------------------------------------- - // Now clean up - //--------------------------------------------------------------------------- - FileSystem fs( url ); - CPPUNIT_ASSERT_XRDST( WaitFor( Rm( fs, "/data/chkpttest.txt" ) ) ); -} - 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 e54a696c841..00000000000 --- a/tests/XrdClTests/PostMasterTest.cc +++ /dev/null @@ -1,581 +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 PostMasterFetch - { - public: - 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; - } - }; -} - -//------------------------------------------------------------------------------ -// 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(); - CPPUNIT_ASSERT( 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 ); - CPPUNIT_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; - CPPUNIT_ASSERT_XRDST( msgHandlers[i].WaitFor( msg ) ); - ServerResponse *resp = (ServerResponse *)msg.GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( msg.GetSize() == 8 ); - } - return 0; -} - -//------------------------------------------------------------------------------ -// Threading test -//------------------------------------------------------------------------------ -void 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 -//------------------------------------------------------------------------------ -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 ); - - PostMasterFetch pmfetch; - PostMaster *postMaster = pmfetch.Get(); - - 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; - 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 ); - - CPPUNIT_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler1, false, expires ) ); - - CPPUNIT_ASSERT_XRDST( msgHandler1.WaitFor( m2 ) ); - ServerResponse *resp = (ServerResponse *)m2.GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( 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 ); - 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, - expires ) ); - CPPUNIT_ASSERT_XRDST_NOTOK( msgHandler3.WaitFor( m2 ), errConnectionError ); - - //---------------------------------------------------------------------------- - // Test the transport queries - //---------------------------------------------------------------------------- - AnyObject nameObj, sidMgrObj; - Status st1, st2; - const char *name = 0; - - CPPUNIT_ASSERT_XRDST( postMaster->QueryTransport( host, - TransportQuery::Name, - nameObj ) ); - nameObj.Get( name ); - - CPPUNIT_ASSERT( name ); - CPPUNIT_ASSERT( !::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 ); - CPPUNIT_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler4, false, expires ) ); - - CPPUNIT_ASSERT_XRDST( msgHandler4.WaitFor( m2 ) ); - 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 ); - SyncMsgHandler msgHandler5; - msgHandler5.SetFilter( 1, 2 ); - msgHandler5.SetExpiration( expires ); - CPPUNIT_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler5, false, expires ) ); - - CPPUNIT_ASSERT_XRDST( msgHandler5.WaitFor( m2 ) ); - 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 - //---------------------------------------------------------------------------- - 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 ); - 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 ); -#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 ); - - PostMasterFetch pmfetch; - PostMaster *postMaster = pmfetch.Get(); - - 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 - //---------------------------------------------------------------------------- - SyncMsgHandler msgHandler1; - msgHandler1.SetFilter( 1, 2 ); - msgHandler1.SetExpiration( expires ); - Message *m = CreatePing( 1, 2 ); - CPPUNIT_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; - - CPPUNIT_ASSERT_XRDST( postMaster->Send( url2, m, &msgHandler2, false, expires ) ); - CPPUNIT_ASSERT_XRDST_NOTOK( msgHandler2.WaitFor( m2 ), errConnectionError ); - - //---------------------------------------------------------------------------- - // Try on a good one - //---------------------------------------------------------------------------- - SyncMsgHandler msgHandler3; - msgHandler3.SetFilter( 1, 2 ); - msgHandler3.SetExpiration( 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 ); - 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 e6a4516aafd..00000000000 --- a/tests/XrdClTests/SocketTest.cc +++ /dev/null @@ -1,307 +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 "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 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 2a446ad84de..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, lastUrl; - threadData[i].file->GetProperty( "LastURL", lastUrl ); - CPPUNIT_ASSERT_XRDST( Utils::GetRemoteCheckSum( - remoteSum, "zcrc32", lastUrl ) ); - 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 7bec1130f41..00000000000 --- a/tests/XrdClTests/UtilsTest.cc +++ /dev/null @@ -1,264 +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/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( AnyTest ); - CPPUNIT_TEST( TaskManagerTest ); - CPPUNIT_TEST( SIDManagerTest ); - CPPUNIT_TEST( PropertyListTest ); - CPPUNIT_TEST_SUITE_END(); - void AnyTest(); - void TaskManagerTest(); - void SIDManagerTest(); - void PropertyListTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( UtilsTest ); - -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; - 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]; - - 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 ); -} - -//------------------------------------------------------------------------------ -// Property List 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/XrdClTests/tls/CMakeLists.txt b/tests/XrdClTests/tls/CMakeLists.txt deleted file mode 100644 index 93b3e145a10..00000000000 --- a/tests/XrdClTests/tls/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ - - -#------------------------------------------------------------------------------- -# xrdcopy -#------------------------------------------------------------------------------- -if( NOT XRDCL_ONLY ) - -add_executable( - xrdcl-tls - xrdcl-tls.cc ) - -target_link_libraries( - xrdcl-tls - XrdCl - XrdPosix - XrdXml - XrdAppUtils - ${CMAKE_DL_LIBS} - ${CMAKE_THREAD_LIBS_INIT} ) - - -add_executable( - xrdsrv-tls - xrdsrv-tls.cc ) - -target_link_libraries( - xrdsrv-tls - XrdUtils - OpenSSL::SSL - ${CMAKE_THREAD_LIBS_INIT} ) - -endif() diff --git a/tests/XrdClTests/tls/README.md b/tests/XrdClTests/tls/README.md deleted file mode 100644 index 8029915d96c..00000000000 --- a/tests/XrdClTests/tls/README.md +++ /dev/null @@ -1,19 +0,0 @@ -The test requires a SSL certificate and private key. These can be generated -using the 'openssl' program using these steps: - -1. Generate the private key, this is what we normally keep secret: -```console - openssl genrsa -des3 -passout pass:x -out server.pass.key 2048 - openssl rsa -passin pass:x -in server.pass.key -out server.key - rm -f server.pass.key -``` -2. Next generate the CSR. We can leave the password empty when prompted - (because this is self-sign): -```console - openssl req -new -key server.key -out server.csr -``` -3. Next generate the self signed certificate: -```console - openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt - rm -f server.csr -``` diff --git a/tests/XrdClTests/tls/xrdcl-tls.cc b/tests/XrdClTests/tls/xrdcl-tls.cc deleted file mode 100644 index 0c0d1e2c191..00000000000 --- a/tests/XrdClTests/tls/xrdcl-tls.cc +++ /dev/null @@ -1,70 +0,0 @@ -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClDefaultEnv.hh" - -#include - -int main( int argc, char *argv[] ) -{ -// XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); -// env->PutInt( "SubStreamsPerChannel", 2 ); - - XrdCl::File f; - - std::cout << "Do open ... " << std::endl; - XrdCl::XRootDStatus st = f.Open( "roots://localhost//some/path/to/file", XrdCl::OpenFlags::Update ); - if( !st.IsOK() ) - { - std::cout << "Open failed" << std::endl; - std::cout << st.ToString() << std::endl; - return -1; - } - std::cout << "Open done!" << std::endl; - - std::cout << "Waiting for 5s ..." << std::endl; - sleep( 5 ); - std::cout << "... done waiting!" << std::endl; - - uint64_t offset = 20; - uint32_t size = 20; - uint32_t bytesRead = 0; - char buffer[size]; - - std::cout << "Do the 1st read ... " << std::endl; - st = f.Read( offset, size, buffer, bytesRead ); - if( !st.IsOK() ) - { - std::cout << "Read failed" << std::endl; - std::cout << st.ToString() << std::endl; - return -1; - } - std::cout << "Read done!" << std::endl; - - std::cout << "bytes read : " << bytesRead << std::endl; - std::cout << std::string( buffer, bytesRead ) << std::endl; - - std::cout << "Do the 2nd read ... " << std::endl; - st = f.Read( offset, size, buffer, bytesRead ); - if( !st.IsOK() ) - { - std::cout << "Read failed" << std::endl; - std::cout << st.ToString() << std::endl; - return -1; - } - std::cout << "Read done!" << std::endl; - - std::cout << "bytes read : " << bytesRead << std::endl; - std::cout << std::string( buffer, bytesRead ) << std::endl; - - std::cout << "Do close ... " << std::endl; - st = f.Close(); - if( !st.IsOK() ) - { - std::cout << "Close failed" << std::endl; - std::cout << st.ToString() << std::endl; - return -1; - } - std::cout << "Close done!" << std::endl; - - return 0; -} diff --git a/tests/XrdClTests/tls/xrdsrv-tls.cc b/tests/XrdClTests/tls/xrdsrv-tls.cc deleted file mode 100644 index 334de50c71d..00000000000 --- a/tests/XrdClTests/tls/xrdsrv-tls.cc +++ /dev/null @@ -1,698 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdTls/XrdTlsContext.hh" - - -struct StdIO -{ - void write( const std::string &str ) - { - std::unique_lock lck( mtx ); - std::cout << str << std::endl; - } - - private: - - std::mutex mtx; -} stdio; - -struct SocketIO -{ - SocketIO() : tlson( false ), socket( -1 ), ssl( 0 ) - { - } - - ~SocketIO() - { - if( ssl ) SSL_free( ssl ); - } - - void SetSocket( int socket ) - { - this->socket = socket; - } - - void TlsHandShake() - { - std::stringstream ss; - ss << std::endl << __func__ << std::endl << std::endl; - static XrdTlsContext tlsctx("server.crt", "server.key"); - - BIO *sbio = BIO_new_socket( socket, BIO_NOCLOSE ); - ssl = SSL_new( static_cast(tlsctx.Context()) ); - SSL_set_accept_state( ssl ); - SSL_set_bio( ssl, sbio, sbio ); - ss << "SSL_accept is waiting." << std::endl; - int rc = SSL_accept( ssl ); - int code = SSL_get_error( ssl, rc ); - - if( code != SSL_ERROR_NONE ) - { - char *str = ERR_error_string( code , 0 ); - std::cerr << "SSL_accept failed : code : " << code << std::endl; - std::cerr << str << std::endl; - exit( -1 ); - } - - tlson = true; - - ss << "SSL hand shake done!" << std::endl; - stdio.write( ss.str() ); - } - - int read( void *buffer, int size ) - { - int ret = 0; - - char *buff = static_cast( buffer ); - while ( size != 0 ) - { - int rc = 0; - if( tlson ) - rc = SSL_read( ssl, buff, size ); - else - rc = ::read( socket, buff, size ); - - if( rc <= 0 ) - break; - - ret += rc; - buff += rc; - size -= rc; - } - - return ret; - } - - int write( const void *buffer, int size ) - { - int rc = 0; - if( tlson ) - rc = SSL_write( ssl, buffer, size ); - else - rc = ::write( socket, buffer, size ); - - return rc; - } - - void renegotiate() - { - if( tlson ) - SSL_renegotiate( ssl ); - } - - private: - - bool tlson; - int socket; - SSL *ssl; - -} mainio, dataio; - -void die(const char *msg) { - perror(msg); - exit(1); -} - -void handle_error(const char *file, int lineno, const char *msg) { - fprintf(stderr, "** %s:%i %s\n", file, lineno, msg); - ERR_print_errors_fp(stderr); - exit(-1); -} - -#define int_error(msg) handle_error(__FILE__, __LINE__, msg) - -void DoInitHS( SocketIO &io ) -{ - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 2 * sizeof( kXR_int32 ) ); - io.write( &respHdr, sizeof(ServerResponseHeader) ); - - ServerInitHandShake hs; - memset( &hs, 0, sizeof( ServerInitHandShake ) ); - hs.protover = htonl( 0x500 ); - io.write( &hs.protover, sizeof( kXR_int32 ) ); - hs.msgval = htonl( kXR_DataServer ); - io.write( &hs.msgval, sizeof( kXR_int32 ) ); -} - -void HandleProtocolReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - std::stringstream ss; - ss << __func__ << std::endl; - - ClientProtocolRequest *req = (ClientProtocolRequest*)hdr; - - ss << "Client protocol version : " << std::hex << ntohl(req->clientpv) << std::dec << std::endl; - ss << "Flags : " << (int)req->flags << std::endl; - ss << "Expect : " << (int)req->expect << std::endl; - stdio.write( ss.str() ); - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( sizeof( ServerResponseBody_Protocol ) ); - io.write( &respHdr, sizeof(ServerResponseHeader) ); - - kXR_int32 flags = kXR_DataServer | kXR_haveTLS | kXR_gotoTLS | kXR_tlsLogin;// | kXR_tlsData; - std::cout << "Server flags = " << flags << std::endl; - - ServerResponseBody_Protocol body; - body.pval = htonl( 0x500 ); - body.flags = htonl( flags ); - io.write( &body, sizeof(ServerResponseBody_Protocol) ); - - if( &io == &mainio && ( flags & kXR_tlsLogin ) ) - io.TlsHandShake(); - else if( &io == &dataio && (flags & kXR_tlsData ) ) - io.TlsHandShake(); -} - -void HandleLoginReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientLoginRequest *req = (ClientLoginRequest*) hdr; - - std::stringstream ss; - ss << __func__ << std::endl; - ss << "Client PID : " << std::dec << ntohl( req->pid ) << std::endl; - - char *buffer = new char[hdr->dlen]; - io.read( buffer, hdr->dlen ); - ss << "Token : " << std::string( buffer, hdr->dlen ) << std::endl; - stdio.write( ss.str() ); - delete[] buffer; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 16 ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - - ServerResponseBody_Login body; - memset( body.sessid, 0, 16 ); - io.write( &body, 16 ); -} - -void HandleOpenReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientOpenRequest *req = (ClientOpenRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - ss << "Open mode : 0x" << std::hex << ntohs( req->mode ) << std::dec << std::endl; - - static const std::string statstr = "ABCD 1024 0 0"; - - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - ss << "Path : " << std::string( buffer, req->dlen ) << std::endl; - delete[] buffer; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 12 + statstr.size() ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - - ServerResponseBody_Open body; - memset( body.fhandle, 0, 4 ); - memset( body.cptype, 0, 4 ); - body.cpsize = 0; - io.write( &body, 12 ); - - io.write( statstr.c_str(), statstr.size() ); - - ss << "Renegotiating session ..." << std::endl; - stdio.write( ss.str() ); - io.renegotiate(); //< test session re-negotiation !!! -} - -void HandleReadReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientReadRequest *req = (ClientReadRequest*) hdr; - std::stringstream ss; - ss << __func__ << " : " << ( &io == &dataio ? "data stream." : "control stream." ) << std::endl; - - req->rlen = ntohl( req->rlen ); - req->offset = ntohll( req->offset ); - ss << std::dec << "Read " << req->rlen << " bytes at " << req->offset << " offset" << std::endl; - - static const std::string readstr = "ala ma kota, a ola ma psa, a ela ma rybke"; - - kXR_char pathid = 0; - if( req->dlen ) - { - ss << "alen : " << req->dlen << std::endl; - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - - read_args* rargs = (read_args*)buffer; - pathid = rargs->pathid; - ss << "Path ID : " << (int)rargs->pathid << std::endl; - delete[] buffer; - } - - int dlen = req->rlen > int( readstr.size() ) ? readstr.size() : req->rlen; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( dlen ); - - // pick up the I/O based on the pathid - if( !pathid ) - { - mainio.write( &respHdr, sizeof( ServerResponseHeader ) ); - mainio.write( readstr.c_str(), dlen ); - } - else - { - ss << "Writing to data stream!" << std::endl; - dataio.write( &respHdr, sizeof( ServerResponseHeader ) ); - dataio.write( readstr.c_str(), dlen ); - } - - stdio.write( ss.str() ); -} - -void HandleCloseReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientReadRequest *req = (ClientReadRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - stdio.write( ss.str() ); - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = 0; - io.write( &respHdr, sizeof( ServerResponseHeader ) ); -} - -void HandleBindReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientBindRequest *req = (ClientBindRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - stdio.write( ss.str() ); - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 1 ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - - ServerResponseBody_Bind body; - body.substreamid = 1; - io.write( &body, sizeof( ServerResponseBody_Bind ) ); -} - - -int HandleRequest( SocketIO &io, int iterations ) -{ - char buffer[1024] = {0}; - std::fill( buffer, buffer + 1024, 'A' ); - int valread = io.read( buffer, 20 ); - std::stringstream ss; - ss << "valread : " << valread << std::endl; - if( valread != 20 ) return -1; - ClientInitHandShake *init = (ClientInitHandShake*)buffer; - ss << "First : " << ntohl( init->first ) << std::endl; - ss << "Second : " << ntohl( init->second ) << std::endl; - ss << "Third : " << ntohl( init->third ) << std::endl; - ss << "Fourth : " << ntohl( init->fourth ) << std::endl; - ss << "Fifth : " << ntohl( init->fifth ) << std::endl; - stdio.write( ss.str() ); - - DoInitHS( io ); - - int count = 0; - while( count < iterations ) - { - ++count; - - std::stringstream ss; - ss << std::endl; - ss << "Step : " << count << std::endl; - ss << "Waiting for client ..." << std::endl; - ss << "reading : " << sizeof( ClientRequestHdr ) << " bytes." << std::endl; - valread = io.read( buffer, sizeof( ClientRequestHdr ) ); - ss << "valread : " << valread << std::endl; - if( valread < 0 ) - { - return -1; - } - else if( valread < 8 ) - { - std::cout << "Got bogus header!" << std::endl; - std::cout << std::string( buffer, valread ) << std::endl; - return -1; - } - - ClientRequestHdr *hdr = (ClientRequestHdr*)buffer; - hdr->dlen = ntohl( hdr->dlen ); - hdr->requestid = ntohs( hdr->requestid ); - - ss << "Got request: " << hdr->requestid << std::endl; - stdio.write( ss.str() ); - - switch( hdr->requestid ) - { - case kXR_auth: - { - stdio.write( "Got kXR_auth!" ); - break; - } - - case kXR_query: - { - stdio.write( "Got kXR_query!" ); - break; - } - - case kXR_chmod: - { - stdio.write( "Got kXR_chmod!" ); - break; - } - - case kXR_close: - { - stdio.write( "Got kXR_close!" ); - HandleCloseReq( io, hdr ); - break; - } - - case kXR_dirlist: - { - stdio.write( "Got kXR_dirlist!" ); - break; - } - - case kXR_gpfile: - { - stdio.write( "Got kXR_gpfile!" ); - break; - } - - case kXR_protocol: - { - stdio.write( "Got kXR_protocol!" ); - HandleProtocolReq( io, hdr ); - break; - } - - case kXR_login: - { - stdio.write( "Got kXR_login!" ); - HandleLoginReq( io, hdr ); - break; - } - - case kXR_mkdir: - { - stdio.write( "Got kXR_mkdir!" ); - break; - } - - case kXR_mv: - { - stdio.write( "Got kXR_mv!" ); - break; - } - - case kXR_open: - { - stdio.write( "Got kXR_open!" ); - HandleOpenReq( io, hdr ); - break; - } - - case kXR_ping: - { - stdio.write( "Got kXR_ping!" ); - break; - } - - case kXR_chkpoint: - { - stdio.write( "Got kXR_chkpoint!" ); - break; - } - - case kXR_read: - { - stdio.write( "Got kXR_read!" ); - HandleReadReq( io, hdr ); - break; - } - - case kXR_rm: - { - stdio.write( "Got kXR_rm!" ); - break; - } - - case kXR_rmdir: - { - stdio.write( "Got kXR_rmdir!" ); - break; - } - - case kXR_sync: - { - stdio.write( "Got kXR_sync!" ); - break; - } - - case kXR_stat: - { - stdio.write( "Got kXR_stat!" ); - break; - } - - case kXR_set: - { - stdio.write( "Got kXR_set!" ); - break; - } - - case kXR_write: - { - stdio.write( "Got kXR_write!" ); - break; - } - - case kXR_fattr: - { - stdio.write( "Got kXR_fattr!" ); - break; - } - - case kXR_prepare: - { - stdio.write( "Got kXR_prepare!" ); - break; - } - - case kXR_statx: - { - stdio.write( "Got kXR_statx!" ); - break; - } - - case kXR_endsess: - { - stdio.write( "Got kXR_endsess!" ); - break; - } - - case kXR_bind: - { - stdio.write( "Got kXR_bind!" ); - HandleBindReq( io, hdr ); - break; - } - - case kXR_readv: - { - stdio.write( "Got kXR_readv!" ); - break; - } - - case kXR_pgwrite: - { - stdio.write( "Got kXR_pgwrite!" ); - break; - } - - case kXR_locate: - { - stdio.write( "Got kXR_locate!" ); - break; - } - - case kXR_truncate: - { - stdio.write( "Got kXR_truncate!" ); - break; - } - - case kXR_sigver: - { - stdio.write( "Got kXR_sigver!" ); - break; - } - - case kXR_pgread: - { - stdio.write( "Got kXR_pgread!" ); - break; - } - - case kXR_writev: - { - stdio.write( "Got kXR_writev!" ); - break; - } - }; - - } - - return 0; -} - - -void* control_stream( void *arg ) -{ - std::stringstream ss; - ss << '\n' << __func__ << '\n'; - stdio.write( ss.str() ); - - int socket = *(int*)arg; - mainio.SetSocket( socket ); - int rc = HandleRequest( mainio, 6 ); - return new int( rc ); -} - -void* data_stream( void *arg ) -{ - std::stringstream ss; - ss << '\n' << __func__ << '\n'; - stdio.write( ss.str() ); - - int socket = *(int*)arg; - dataio.SetSocket( socket ); - int rc = HandleRequest( dataio, 2 ); - return new int( rc ); -} - - -// TODO we need control thread and data thread !!! - -int main(int argc, char const *argv[]) -{ - int server_fd, new_socket; - struct sockaddr_in address; - int opt = 1; - int addrlen = sizeof(address); - - // Creating socket file descriptor - if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) - { - perror("socket failed"); - exit(EXIT_FAILURE); - } - - // Forcefully attaching socket to the port 8080 - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, - &opt, sizeof(opt))) - { - perror("setsockopt"); - exit(EXIT_FAILURE); - } - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons( 1094 ); - - // Forcefully attaching socket to the port 8080 - if (bind(server_fd, (struct sockaddr *)&address, - sizeof(address))<0) - { - perror("bind failed"); - exit(EXIT_FAILURE); - } - if (listen(server_fd, 3) < 0) - { - perror("listen"); - exit(EXIT_FAILURE); - } - - pthread_t threads[2]; - - int count = 0; - while( count < 2 ) - { - std::stringstream ss; - ss << "Waiting to accept new TCP connection!" << std::endl; - if ((new_socket = accept(server_fd, (struct sockaddr *)&address, // TODO - (socklen_t*)&addrlen))<0) - { - perror("accept"); - exit(EXIT_FAILURE); - } - ss << "New TCP connection accepted!" << std::endl; - stdio.write( ss.str() ); - - if( count == 0 ) - pthread_create( &threads[count], 0, control_stream, &new_socket ); - else - pthread_create( &threads[count], 0, data_stream, &new_socket ); - ++count; - } - - count = 0; - while( count < 2 ) - { - void *ret = 0; - pthread_join( threads[count], &ret ); - std::unique_ptr rc( (int*)ret ); - if( *rc ) return *rc; - ++count; - } - - sleep( 2 ); - - std::cout << "The End." << std::endl; - - return 0; -} diff --git a/tests/XrdClTests/wrt/xrdsrv-dio.cc b/tests/XrdClTests/wrt/xrdsrv-dio.cc deleted file mode 100644 index 6ad7f83eecc..00000000000 --- a/tests/XrdClTests/wrt/xrdsrv-dio.cc +++ /dev/null @@ -1,602 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - - -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdTls/XrdTlsContext.hh" - -struct StdIO -{ - void write( const std::string &str ) - { - std::unique_lock lck( mtx ); - std::cout << str << std::endl; - } - - private: - - std::mutex mtx; -} stdio; - -struct SocketIO -{ - SocketIO( int socket ) : socket( socket ) - { - } - - ~SocketIO() - { - } - - int read( void *buffer, int size ) - { - int ret = 0; - - char *buff = static_cast( buffer ); - while ( size != 0 ) - { - int rc = ::read( socket, buff, size ); - - if( rc <= 0 ) - break; - - ret += rc; - buff += rc; - size -= rc; - } - - return ret; - } - - int write( const void *buffer, int size ) - { - int rc = ::write( socket, buffer, size ); - return rc; - } - - void set_fn( const std::string &fn ) - { - this->fn = fn; - } - - const std::string& get_fn() - { - return fn; - } - - private: - int socket; - std::string fn; - -}; - -struct wrt_queue -{ - wrt_queue() : working( true ), worker( work, this ) - { - } - - ~wrt_queue() - { - working = false; - cv.notify_all(); - worker.join(); - } - - static void work( wrt_queue *myself ) - { - while( myself->working ) - { - wrt_request req = myself->pop(); - if( !myself->working ) return; - int rc = pwrite( req.fd, req.buf, req.len, req.off ); - if( rc < 0 ) - { - stdio.write( std::to_string( req.fd ) + ", " + std::to_string( errno ) ); - stdio.write( std::string( "Write failed: " ) + strerror( errno ) ); - } - free( req.buf ); - myself->done(); - } - } - - struct wrt_request - { - wrt_request( int fd, char *buf, size_t len, off_t off ) : - fd( fd ), buf( buf ), len( len ), off( off ) - { - } - - wrt_request() : fd( -1 ), buf( nullptr ), len( 0 ), off( 0 ){ } - - int fd; - char *buf; - size_t len; - off_t off; - }; - - void write( int fd, char *buf, size_t len, off_t off ) - { - wrt_request req( fd, buf, len, off ); - push( req ); - } - - void push( wrt_request &req ) - { - std::unique_lock lck( mtx ); - q.push( req ); - cv.notify_all(); - } - - wrt_request pop() - { - std::unique_lock lck( mtx ); - while( q.empty() && working ) - cv.wait( lck ); - if( !working ) return wrt_request(); - wrt_request req = q.front(); - q.pop(); - return req; - } - - void wait_done() - { - std::unique_lock lck( mtx2 ); - while( !q.empty() ) - { - cv2.wait( lck ); - } - std::cout << "done waiting" << std::endl; - } - - void done() - { - std::unique_lock lck( mtx2 ); - cv2.notify_all(); - } - - bool working; - std::thread worker; - std::queue q; - std::mutex mtx; - std::condition_variable cv; - - std::mutex mtx2; - std::condition_variable cv2; -}; - -void handle_error(const char *file, int lineno, const char *msg) { - fprintf(stderr, "** %s:%i %s\n", file, lineno, msg); - exit(-1); -} - -#define int_error(msg) handle_error(__FILE__, __LINE__, msg) - -void DoInitHS( SocketIO &io ) -{ - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 2 * sizeof( kXR_int32 ) ); - io.write( &respHdr, sizeof(ServerResponseHeader) ); - - ServerInitHandShake hs; - memset( &hs, 0, sizeof( ServerInitHandShake ) ); - hs.protover = htonl( 0x500 ); - io.write( &hs.protover, sizeof( kXR_int32 ) ); - hs.msgval = htonl( kXR_DataServer ); - io.write( &hs.msgval, sizeof( kXR_int32 ) ); -} - -void HandleProtocolReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - std::stringstream ss; - ss << __func__ << std::endl; - - ClientProtocolRequest *req = (ClientProtocolRequest*)hdr; - - ss << "Client protocol version : " << std::hex << ntohl(req->clientpv) << std::dec << std::endl; - ss << "Flags : " << (int)req->flags << std::endl; - ss << "Expect : " << (int)req->expect << std::endl; - stdio.write( ss.str() ); - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( sizeof( ServerResponseBody_Protocol ) ); - io.write( &respHdr, sizeof(ServerResponseHeader) ); - - kXR_int32 flags = kXR_DataServer; - std::cout << "Server flags = " << flags << std::endl; - - ServerResponseBody_Protocol body; - body.pval = htonl( 0x500 ); - body.flags = htonl( flags ); - io.write( &body, sizeof(ServerResponseBody_Protocol) ); -} - -void HandleLoginReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientLoginRequest *req = (ClientLoginRequest*) hdr; - - std::stringstream ss; - ss << __func__ << std::endl; - ss << "Client PID : " << std::dec << ntohl( req->pid ) << std::endl; - - char *buffer = new char[hdr->dlen]; - io.read( buffer, hdr->dlen ); - ss << "Token : " << std::string( buffer, hdr->dlen ) << std::endl; - stdio.write( ss.str() ); - delete[] buffer; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 16 ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - - ServerResponseBody_Login body; - memset( body.sessid, 0, 16 ); - io.write( &body, 16 ); -} - -int HandleOpenReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientOpenRequest *req = (ClientOpenRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - ss << "Open mode : 0x" << std::hex << ntohs( req->mode ) << std::dec << std::endl; - - static const std::string statstr = "ABCD 1024 0 0"; - - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - std::string path( buffer, req->dlen ); - io.set_fn( path ); - ss << "Path : " << std::string( buffer, req->dlen ) << std::endl; - delete[] buffer; - - ss << "opening : " << path << std::endl; - int fd = open( path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_DIRECT, 0664 ); - if( fd < 0 ) - stdio.write( strerror( errno ) ); - else - ss << "file opened : " << fd << std::endl; - - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 12 + statstr.size() ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - - ServerResponseBody_Open body; - memset( body.fhandle, 0, 4 ); - memset( body.cptype, 0, 4 ); - body.cpsize = 0; - io.write( &body, 12 ); - io.write( statstr.c_str(), statstr.size() ); - stdio.write( ss.str() ); - - return fd; -} - - void HandleStatReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientStatRequest *req = (ClientStatRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - - static const std::string statstr = "ABCD 1024 0 0"; - - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - std::string path( buffer, req->dlen ); - ss << "Path : " << std::string( buffer, req->dlen ) << std::endl; - delete[] buffer; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( statstr.size() ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - io.write( statstr.c_str(), statstr.size() ); - stdio.write( ss.str() ); -} - -void HandleWriteReq( SocketIO &io, ClientRequestHdr *hdr, int fd, wrt_queue &wq ) -{ - ClientWriteRequest *req = (ClientWriteRequest*) hdr; - std::stringstream ss; - ss << __func__ << " : " << "control stream." << std::endl; - ss << "req->dlen = " << req->dlen << std::endl; - req->offset = ntohll( req->offset ); - ss << std::dec << "Read " << req->dlen << " bytes from socket."; - void *ptr = nullptr; - posix_memalign( &ptr, 512, req->dlen ); - char *buffer = (char*)ptr; - io.read( buffer, req->dlen ); - wq.write( fd, buffer, req->dlen, req->offset ); - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = 0; - - // pick up the I/O based on the pathid - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - //stdio.write( ss.str() ); -} - -void HandleReadReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientReadRequest *req = (ClientReadRequest*) hdr; - std::stringstream ss; - ss << __func__ << " : " << "control stream." << std::endl; - - req->rlen = ntohl( req->rlen ); - req->offset = ntohll( req->offset ); - ss << std::dec << "Read " << req->rlen << " bytes at " << req->offset << " offset" << std::endl; - - static const std::string readstr = "ala ma kota, a ola ma psa, a ela ma rybke"; - - if( req->dlen ) - { - ss << "alen : " << req->dlen << std::endl; - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - - read_args* rargs = (read_args*)buffer; - ss << "Path ID : " << (int)rargs->pathid << std::endl; - delete[] buffer; - } - - int dlen = req->rlen > int( readstr.size() ) ? readstr.size() : req->rlen; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( dlen ); - - // pick up the I/O based on the pathid - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - io.write( readstr.c_str(), dlen ); - stdio.write( ss.str() ); -} - -void HandleCloseReq( SocketIO &io, ClientRequestHdr *hdr, int fd, wrt_queue &wq ) -{ - ClientReadRequest *req = (ClientReadRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - ss << "Closing: " << io.get_fn() << std::endl; - stdio.write( ss.str() ); - - wq.wait_done(); - int rc = close( fd ); - if( rc < 0 ) - stdio.write( strerror( errno ) ); - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = 0; - io.write( &respHdr, sizeof( ServerResponseHeader ) ); -} - - -int HandleRequest( SocketIO &io ) -{ - char buffer[1024] = {0}; - std::fill( buffer, buffer + 1024, 'A' ); - int valread = io.read( buffer, 20 ); - std::stringstream ss; - ss << "valread : " << valread << std::endl; - if( valread != 20 ) return -1; - ClientInitHandShake *init = (ClientInitHandShake*)buffer; - ss << "First : " << ntohl( init->first ) << std::endl; - ss << "Second : " << ntohl( init->second ) << std::endl; - ss << "Third : " << ntohl( init->third ) << std::endl; - ss << "Fourth : " << ntohl( init->fourth ) << std::endl; - ss << "Fifth : " << ntohl( init->fifth ) << std::endl; - stdio.write( ss.str() ); - - DoInitHS( io ); - - wrt_queue wrtq; - int fd = -1; - - while( true ) - { - std::stringstream ss; - ss << std::endl; - ss << "Waiting for client ..." << std::endl; - ss << "reading : " << sizeof( ClientRequestHdr ) << " bytes." << std::endl; - valread = io.read( buffer, sizeof( ClientRequestHdr ) ); - ss << "valread : " << valread << std::endl; - if( valread < 0 ) - { - return -1; - } - else if( valread == 0 ) - { - stdio.write( "client terminated the connection"); - return 0; - } - else if( valread < 8 ) - { - std::cout << "Got bogus header : " << valread << std::endl; - std::cout << std::string( buffer, valread ) << std::endl; - return -1; - } - - ClientRequestHdr *hdr = (ClientRequestHdr*)buffer; - hdr->dlen = ntohl( hdr->dlen ); - hdr->requestid = ntohs( hdr->requestid ); - - ss << "Got request: " << hdr->requestid << std::endl; - //stdio.write( ss.str() ); - - switch( hdr->requestid ) - { - case kXR_close: - { - stdio.write( "Got kXR_close!" ); - HandleCloseReq( io, hdr, fd, wrtq ); - fd = -1; - break; - } - - case kXR_protocol: - { - stdio.write( "Got kXR_protocol!" ); - HandleProtocolReq( io, hdr ); - break; - } - - case kXR_login: - { - stdio.write( "Got kXR_login!" ); - HandleLoginReq( io, hdr ); - break; - } - - case kXR_open: - { - stdio.write( "Got kXR_open!" ); - fd = HandleOpenReq( io, hdr ); - break; - } - - case kXR_read: - { - stdio.write( "Got kXR_read!" ); - HandleReadReq( io, hdr ); - break; - } - - case kXR_stat: - { - stdio.write( "Got kXR_stat!" ); - HandleStatReq( io, hdr ); - break; - } - - case kXR_write: - { - //stdio.write( "Got kXR_write!" ); - HandleWriteReq( io, hdr, fd, wrtq ); - break; - } - - default: - { - stdio.write( "Got unsupported request!" ); - break; - } - }; - } - - return 0; -} - - -void control_stream( int socket ) -{ - std::stringstream ss; - ss << '\n' << __func__ << '\n'; - stdio.write( ss.str() ); - SocketIO io( socket ); - HandleRequest( io ); -} - - -int main(int argc, char const *argv[]) -{ - int server_fd, new_socket; - struct sockaddr_in address; - int opt = 1; - int addrlen = sizeof(address); - - // Creating socket file descriptor - if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) - { - perror("socket failed"); - exit(EXIT_FAILURE); - } - - // Forcefully attaching socket to the port 8080 - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, - &opt, sizeof(opt))) - { - perror("setsockopt"); - exit(EXIT_FAILURE); - } - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons( 2094 ); - - // Forcefully attaching socket to the port 8080 - if (bind(server_fd, (struct sockaddr *)&address, - sizeof(address))<0) - { - perror("bind failed"); - exit(EXIT_FAILURE); - } - if (listen(server_fd, 3) < 0) - { - perror("listen"); - exit(EXIT_FAILURE); - } - - std::list threads; - - while( true ) - { - std::stringstream ss; - ss << "Waiting to accept new TCP connection!" << std::endl; - stdio.write( ss.str() ); - if ((new_socket = accept(server_fd, (struct sockaddr *)&address, // TODO - (socklen_t*)&addrlen))<0) - { - perror("accept"); - exit(EXIT_FAILURE); - } - ss << "New TCP connection accepted!" << std::endl; - stdio.write( ss.str() ); - - threads.emplace_back( control_stream, new_socket ); - } - - std::cout << "The End." << std::endl; - - return 0; -} diff --git a/tests/XrdClTests/wrt/xrdsrv-wrt.cc b/tests/XrdClTests/wrt/xrdsrv-wrt.cc deleted file mode 100644 index 7eeb5388aac..00000000000 --- a/tests/XrdClTests/wrt/xrdsrv-wrt.cc +++ /dev/null @@ -1,563 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - - -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdTls/XrdTlsContext.hh" - -struct StdIO -{ - void write( const std::string &str ) - { - std::unique_lock lck( mtx ); - std::cout << str << std::endl; - } - - private: - - std::mutex mtx; -} stdio; - -struct SocketIO -{ - SocketIO( int socket ) : socket( socket ) - { - } - - ~SocketIO() - { - } - - int read( void *buffer, int size ) - { - int ret = 0; - - char *buff = static_cast( buffer ); - while ( size != 0 ) - { - int rc = ::read( socket, buff, size ); - - if( rc <= 0 ) - break; - - ret += rc; - buff += rc; - size -= rc; - } - - return ret; - } - - int write( const void *buffer, int size ) - { - int rc = ::write( socket, buffer, size ); - return rc; - } - - private: - int socket; - -}; - -struct wrt_queue -{ - wrt_queue() : working( true ), worker( work, this ) - { - } - - ~wrt_queue() - { - working = false; - cv.notify_all(); - worker.join(); - } - - static void work( wrt_queue *myself ) - { - while( myself->working ) - { - wrt_request req = myself->pop(); - if( !myself->working ) return; - int rc = pwrite( req.fd, req.buf, req.len, req.off ); - if( rc < 0 ) - stdio.write( strerror( errno ) ); - delete[] req.buf; - } - } - - struct wrt_request - { - wrt_request( int fd, const char *buf, size_t len, off_t off ) : - fd( fd ), buf( buf ), len( len ), off( off ) - { - } - - wrt_request() : fd( -1 ), buf( nullptr ), len( 0 ), off( 0 ){ } - - int fd; - const char *buf; - size_t len; - off_t off; - }; - - void write( int fd, const char *buf, size_t len, off_t off ) - { - wrt_request req( fd, buf, len, off ); - push( req ); - } - - void push( wrt_request &req ) - { - std::unique_lock lck( mtx ); - q.push( req ); - cv.notify_all(); - } - - wrt_request pop() - { - std::unique_lock lck( mtx ); - while( q.empty() && working ) - cv.wait( lck ); - if( !working ) return wrt_request(); - wrt_request req = q.front(); - q.pop(); - return req; - } - - bool working; - std::thread worker; - std::queue q; - std::mutex mtx; - std::condition_variable cv; -}; - -void handle_error(const char *file, int lineno, const char *msg) { - fprintf(stderr, "** %s:%i %s\n", file, lineno, msg); - exit(-1); -} - -#define int_error(msg) handle_error(__FILE__, __LINE__, msg) - -void DoInitHS( SocketIO &io ) -{ - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 2 * sizeof( kXR_int32 ) ); - io.write( &respHdr, sizeof(ServerResponseHeader) ); - - ServerInitHandShake hs; - memset( &hs, 0, sizeof( ServerInitHandShake ) ); - hs.protover = htonl( 0x500 ); - io.write( &hs.protover, sizeof( kXR_int32 ) ); - hs.msgval = htonl( kXR_DataServer ); - io.write( &hs.msgval, sizeof( kXR_int32 ) ); -} - -void HandleProtocolReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - std::stringstream ss; - ss << __func__ << std::endl; - - ClientProtocolRequest *req = (ClientProtocolRequest*)hdr; - - ss << "Client protocol version : " << std::hex << ntohl(req->clientpv) << std::dec << std::endl; - ss << "Flags : " << (int)req->flags << std::endl; - ss << "Expect : " << (int)req->expect << std::endl; - stdio.write( ss.str() ); - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( sizeof( ServerResponseBody_Protocol ) ); - io.write( &respHdr, sizeof(ServerResponseHeader) ); - - kXR_int32 flags = kXR_DataServer; - std::cout << "Server flags = " << flags << std::endl; - - ServerResponseBody_Protocol body; - body.pval = htonl( 0x500 ); - body.flags = htonl( flags ); - io.write( &body, sizeof(ServerResponseBody_Protocol) ); -} - -void HandleLoginReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientLoginRequest *req = (ClientLoginRequest*) hdr; - - std::stringstream ss; - ss << __func__ << std::endl; - ss << "Client PID : " << std::dec << ntohl( req->pid ) << std::endl; - - char *buffer = new char[hdr->dlen]; - io.read( buffer, hdr->dlen ); - ss << "Token : " << std::string( buffer, hdr->dlen ) << std::endl; - stdio.write( ss.str() ); - delete[] buffer; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 16 ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - - ServerResponseBody_Login body; - memset( body.sessid, 0, 16 ); - io.write( &body, 16 ); -} - -int HandleOpenReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientOpenRequest *req = (ClientOpenRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - ss << "Open mode : 0x" << std::hex << ntohs( req->mode ) << std::dec << std::endl; - - static const std::string statstr = "ABCD 1024 0 0"; - - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - std::string path( buffer, req->dlen ); - ss << "Path : " << std::string( buffer, req->dlen ) << std::endl; - delete[] buffer; - - ss << "opening : " << path << std::endl; - int fd = open( path.c_str(), O_WRONLY | O_CREAT, 0664 ); - if( fd < 0 ) - stdio.write( strerror( errno ) ); - else - ss << "file opened : " << fd << std::endl; - - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( 12 + statstr.size() ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - - ServerResponseBody_Open body; - memset( body.fhandle, 0, 4 ); - memset( body.cptype, 0, 4 ); - body.cpsize = 0; - io.write( &body, 12 ); - io.write( statstr.c_str(), statstr.size() ); - stdio.write( ss.str() ); - - return fd; -} - - void HandleStatReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientStatRequest *req = (ClientStatRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - - static const std::string statstr = "ABCD 1024 0 0"; - - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - std::string path( buffer, req->dlen ); - ss << "Path : " << std::string( buffer, req->dlen ) << std::endl; - delete[] buffer; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( statstr.size() ); - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - io.write( statstr.c_str(), statstr.size() ); - stdio.write( ss.str() ); -} - -void HandleWriteReq( SocketIO &io, ClientRequestHdr *hdr, int fd, wrt_queue &wq ) -{ - ClientWriteRequest *req = (ClientWriteRequest*) hdr; - std::stringstream ss; - ss << __func__ << " : " << "control stream." << std::endl; - ss << "req->dlen = " << req->dlen << std::endl; - req->offset = ntohll( req->offset ); - ss << std::dec << "Read " << req->dlen << " bytes from socket."; - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - wq.write( fd, buffer, req->dlen, req->offset ); - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = 0; - - // pick up the I/O based on the pathid - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - stdio.write( ss.str() ); -} - -void HandleReadReq( SocketIO &io, ClientRequestHdr *hdr ) -{ - ClientReadRequest *req = (ClientReadRequest*) hdr; - std::stringstream ss; - ss << __func__ << " : " << "control stream." << std::endl; - - req->rlen = ntohl( req->rlen ); - req->offset = ntohll( req->offset ); - ss << std::dec << "Read " << req->rlen << " bytes at " << req->offset << " offset" << std::endl; - - static const std::string readstr = "ala ma kota, a ola ma psa, a ela ma rybke"; - - kXR_char pathid = 0; - if( req->dlen ) - { - ss << "alen : " << req->dlen << std::endl; - char *buffer = new char[req->dlen]; - io.read( buffer, req->dlen ); - - read_args* rargs = (read_args*)buffer; - pathid = rargs->pathid; - ss << "Path ID : " << (int)rargs->pathid << std::endl; - delete[] buffer; - } - - int dlen = req->rlen > int( readstr.size() ) ? readstr.size() : req->rlen; - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = htonl( dlen ); - - // pick up the I/O based on the pathid - io.write( &respHdr, sizeof( ServerResponseHeader ) ); - io.write( readstr.c_str(), dlen ); - stdio.write( ss.str() ); -} - -void HandleCloseReq( SocketIO &io, ClientRequestHdr *hdr, int fd ) -{ - ClientReadRequest *req = (ClientReadRequest*) hdr; - std::stringstream ss; - ss << __func__ << std::endl; - stdio.write( ss.str() ); - - int rc = close( fd ); - if( rc < 0 ) - stdio.write( strerror( errno ) ); - - ServerResponseHeader respHdr; - memset( &respHdr, 0, sizeof( ServerResponseHeader ) ); - respHdr.streamid[0] = req->streamid[0]; - respHdr.streamid[1] = req->streamid[1]; - respHdr.status = kXR_ok; - respHdr.dlen = 0; - io.write( &respHdr, sizeof( ServerResponseHeader ) ); -} - - -int HandleRequest( SocketIO &io ) -{ - char buffer[1024] = {0}; - std::fill( buffer, buffer + 1024, 'A' ); - int valread = io.read( buffer, 20 ); - std::stringstream ss; - ss << "valread : " << valread << std::endl; - if( valread != 20 ) return -1; - ClientInitHandShake *init = (ClientInitHandShake*)buffer; - ss << "First : " << ntohl( init->first ) << std::endl; - ss << "Second : " << ntohl( init->second ) << std::endl; - ss << "Third : " << ntohl( init->third ) << std::endl; - ss << "Fourth : " << ntohl( init->fourth ) << std::endl; - ss << "Fifth : " << ntohl( init->fifth ) << std::endl; - stdio.write( ss.str() ); - - DoInitHS( io ); - - wrt_queue wrtq; - int fd = -1; - - while( true ) - { - std::stringstream ss; - ss << std::endl; - ss << "Waiting for client ..." << std::endl; - ss << "reading : " << sizeof( ClientRequestHdr ) << " bytes." << std::endl; - valread = io.read( buffer, sizeof( ClientRequestHdr ) ); - ss << "valread : " << valread << std::endl; - if( valread < 0 ) - { - return -1; - } - else if( valread == 0 ) - { - stdio.write( "client terminated the connection"); - return 0; - } - else if( valread < 8 ) - { - std::cout << "Got bogus header : " << valread << std::endl; - std::cout << std::string( buffer, valread ) << std::endl; - return -1; - } - - ClientRequestHdr *hdr = (ClientRequestHdr*)buffer; - hdr->dlen = ntohl( hdr->dlen ); - hdr->requestid = ntohs( hdr->requestid ); - - ss << "Got request: " << hdr->requestid << std::endl; - stdio.write( ss.str() ); - - switch( hdr->requestid ) - { - case kXR_close: - { - stdio.write( "Got kXR_close!" ); - HandleCloseReq( io, hdr, fd ); - fd = -1; - break; - } - - case kXR_protocol: - { - stdio.write( "Got kXR_protocol!" ); - HandleProtocolReq( io, hdr ); - break; - } - - case kXR_login: - { - stdio.write( "Got kXR_login!" ); - HandleLoginReq( io, hdr ); - break; - } - - case kXR_open: - { - stdio.write( "Got kXR_open!" ); - fd = HandleOpenReq( io, hdr ); - break; - } - - case kXR_read: - { - stdio.write( "Got kXR_read!" ); - HandleReadReq( io, hdr ); - break; - } - - case kXR_stat: - { - stdio.write( "Got kXR_stat!" ); - HandleStatReq( io, hdr ); - break; - } - - case kXR_write: - { - stdio.write( "Got kXR_write!" ); - HandleWriteReq( io, hdr, fd, wrtq ); - break; - } - - default: - { - stdio.write( "Got unsupported request!" ); - break; - } - }; - } - - return 0; -} - - -void control_stream( int socket ) -{ - std::stringstream ss; - ss << '\n' << __func__ << '\n'; - stdio.write( ss.str() ); - SocketIO io( socket ); - HandleRequest( io ); -} - - -int main(int argc, char const *argv[]) -{ - int server_fd, new_socket; - struct sockaddr_in address; - int opt = 1; - int addrlen = sizeof(address); - - // Creating socket file descriptor - if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) - { - perror("socket failed"); - exit(EXIT_FAILURE); - } - - // Forcefully attaching socket to the port 8080 - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, - &opt, sizeof(opt))) - { - perror("setsockopt"); - exit(EXIT_FAILURE); - } - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons( 1094 ); - - // Forcefully attaching socket to the port 8080 - if (bind(server_fd, (struct sockaddr *)&address, - sizeof(address))<0) - { - perror("bind failed"); - exit(EXIT_FAILURE); - } - if (listen(server_fd, 3) < 0) - { - perror("listen"); - exit(EXIT_FAILURE); - } - - std::list threads; - - while( true ) - { - std::stringstream ss; - ss << "Waiting to accept new TCP connection!" << std::endl; - stdio.write( ss.str() ); - if ((new_socket = accept(server_fd, (struct sockaddr *)&address, // TODO - (socklen_t*)&addrlen))<0) - { - perror("accept"); - exit(EXIT_FAILURE); - } - ss << "New TCP connection accepted!" << std::endl; - stdio.write( ss.str() ); - - threads.emplace_back( control_stream, new_socket ); - } - - std::cout << "The End." << std::endl; - - return 0; -} From 751a2dec88e9047cb44b41136938be305e578394 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 16:06:03 +0100 Subject: [PATCH 668/773] [Tests] Remove CppUnit code from common tests Convert also the common test code into shared library and link tests against it instead of recompiling the code into each binary. --- tests/CMakeLists.txt | 3 - tests/XrdCl/CMakeLists.txt | 51 +------ tests/XrdCl/GTestXrdHelpers.hh | 24 +-- tests/XrdEc/CMakeLists.txt | 29 +--- tests/XrdEc/{MicroTest.cc => XrdEcTests.cc} | 0 tests/common/CMakeLists.txt | 44 +----- tests/common/CppUnitXrdHelpers.hh | 57 -------- tests/common/PathProcessor.hh | 83 ----------- tests/common/TextRunner.cc | 153 -------------------- 9 files changed, 22 insertions(+), 422 deletions(-) rename tests/XrdEc/{MicroTest.cc => XrdEcTests.cc} (100%) delete mode 100644 tests/common/CppUnitXrdHelpers.hh delete mode 100644 tests/common/PathProcessor.hh delete mode 100644 tests/common/TextRunner.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 84f3a653f12..bb88cc32e26 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,10 +8,7 @@ add_subdirectory(common) add_subdirectory(XrdCl) add_subdirectory(XrdCeph) - -if(BUILD_XRDEC) add_subdirectory(XrdEc) -endif() add_subdirectory(XrdHttpTests) diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index 015f4c92501..6e06487c550 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -3,23 +3,10 @@ add_executable(xrdcl-unit-tests XrdClPoller.cc XrdClSocket.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 -) + XrdTestUtils GTest::GTest GTest::Main) gtest_discover_tests(xrdcl-unit-tests TEST_PREFIX XrdCl::) @@ -45,23 +32,10 @@ add_executable(xrdcl-cluster-tests XrdClPostMasterTest.cc XrdClThreadingTest.cc XrdClZip.cc - ../common/Server.cc - ../common/Utils.cc - ../common/TestEnv.cc ) target_link_libraries(xrdcl-cluster-tests - XrdCl - XrdXml - XrdUtils - ZLIB::ZLIB - GTest::GTest - GTest::Main -) - -target_include_directories(xrdcl-cluster-tests - PRIVATE ${CMAKE_SOURCE_DIR}/src ../common -) + XrdTestUtils GTest::GTest GTest::Main) gtest_discover_tests(xrdcl-cluster-tests TEST_PREFIX XrdCl:: PROPERTIES DEPENDS XRootD_Cluster FIXTURES_REQUIRED XRootD_Cluster) @@ -76,28 +50,11 @@ set(SERIAL_TESTS_FILES # 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 - ) + add_executable(${i}-tests ${i} IdentityPlugIn.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 - ) + XrdTestUtils GTest::GTest GTest::Main) gtest_discover_tests(${i}-tests TEST_PREFIX XrdCl:: PROPERTIES DEPENDS XRootD_Cluster FIXTURES_REQUIRED XRootD_Cluster RUN_SERIAL 1) - endforeach() diff --git a/tests/XrdCl/GTestXrdHelpers.hh b/tests/XrdCl/GTestXrdHelpers.hh index d54ef29c95a..8569289323e 100644 --- a/tests/XrdCl/GTestXrdHelpers.hh +++ b/tests/XrdCl/GTestXrdHelpers.hh @@ -24,43 +24,27 @@ #include #include -/** @brief Equivalent of CPPUNIT_ASSERT_XRDST - * - * Shows the code that we are asserting and its value - * in the final evaluation. - */ +/** 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; \ } -/** @brief Equivalent of CPPUNIT_ASSERT_XRDST_NOTOK - * - * Shows the code that we are asserting and asserts that its - * execution is throwing an error. - */ +/** 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. - */ +/** 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. - */ +/** Shows the code that we are asserting and its error number, in a thread-safe manner. */ #define GTEST_ASSERT_PTHREAD( x ) \ { \ errno = x; \ diff --git a/tests/XrdEc/CMakeLists.txt b/tests/XrdEc/CMakeLists.txt index 0531c35ec43..f8052b4e0cd 100644 --- a/tests/XrdEc/CMakeLists.txt +++ b/tests/XrdEc/CMakeLists.txt @@ -1,25 +1,12 @@ -add_executable(xrdec-unit-tests - MicroTest.cc - ../common/Server.cc - ../common/Utils.cc - ../common/TestEnv.cc -) +if(NOT BUILD_XRDEC) + return() +endif() + +add_executable(xrdec-unit-tests XrdEcTests.cc) target_link_libraries(xrdec-unit-tests - XrdEc - XrdCl - XrdXml - XrdUtils - ZLIB::ZLIB - GTest::GTest - GTest::Main - ${ISAL_LIBRARIES} -) + XrdEc XrdTestUtils GTest::GTest GTest::Main ${ISAL_LIBRARIES}) -target_include_directories(xrdec-unit-tests - PRIVATE ${CMAKE_SOURCE_DIR}/src - PRIVATE ../common - ${ISAL_INCLUDE_DIRS} -) +target_include_directories(xrdec-unit-tests PRIVATE ${ISAL_INCLUDE_DIRS}) -gtest_discover_tests(xrdec-unit-tests TEST_PREFIX XrdCl::) +gtest_discover_tests(xrdec-unit-tests TEST_PREFIX XrdEc::) diff --git a/tests/XrdEc/MicroTest.cc b/tests/XrdEc/XrdEcTests.cc similarity index 100% rename from tests/XrdEc/MicroTest.cc rename to tests/XrdEc/XrdEcTests.cc diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 5497a7116dc..d9501f315e9 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -1,41 +1,9 @@ - - -add_library( - XrdClTestsHelper SHARED - Server.cc Server.hh - Utils.cc Utils.hh +add_library(XrdTestUtils SHARED + Server.cc Server.hh TestEnv.cc TestEnv.hh - CppUnitXrdHelpers.hh) - -target_link_libraries( - XrdClTestsHelper - ${CMAKE_THREAD_LIBS_INIT} - ${CPPUNIT_LIBRARIES} - ZLIB::ZLIB - XrdCl - XrdUtils) - -target_include_directories( - XrdClTestsHelper PUBLIC ${CPPUNIT_INCLUDE_DIRS}) - -add_executable( - test-runner - TextRunner.cc - PathProcessor.hh) - -target_link_libraries( - test-runner - ${CMAKE_DL_LIBS} - ${CPPUNIT_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT}) + Utils.cc Utils.hh) -target_include_directories( - test-runner PRIVATE ${CPPUNIT_INCLUDE_DIRS}) +target_link_libraries(XrdTestUtils ZLIB::ZLIB XrdCl XrdUtils) -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdClTestsHelper test-runner - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +target_include_directories(XrdTestUtils + PUBLIC $) diff --git a/tests/common/CppUnitXrdHelpers.hh b/tests/common/CppUnitXrdHelpers.hh deleted file mode 100644 index b4634433133..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/TextRunner.cc b/tests/common/TextRunner.cc deleted file mode 100644 index 26f5ff86514..00000000000 --- a/tests/common/TextRunner.cc +++ /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 . -//------------------------------------------------------------------------------ - -#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(); - return wasSuccessful ? 0 : 1; -} From b34a4eb78ff9f2d8b772a9c2908b2c2969f062f8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 16:15:21 +0100 Subject: [PATCH 669/773] [CMake,DEB,RPM] Remove CppUnit code from build system Fixes: #2051 --- cmake/FindCppUnit.cmake | 30 ------------------------------ cmake/XRootDFindLibs.cmake | 4 +--- cmake/XRootDSummary.cmake | 2 +- debian/control | 1 - xrootd.spec | 6 ------ 5 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 cmake/FindCppUnit.cmake diff --git a/cmake/FindCppUnit.cmake b/cmake/FindCppUnit.cmake deleted file mode 100644 index cc6e7400191..00000000000 --- a/cmake/FindCppUnit.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# Try to find CPPUnit -# Once done, this will define -# -# CPPUNIT_FOUND - system has cppunit -# CPPUNIT_INCLUDE_DIRS - the cppunit include directories -# CPPUNIT_LIBRARIES - cppunit libraries directories - -find_path( CPPUNIT_INCLUDE_DIRS cppunit/ui/text/TestRunner.h - HINTS - ${CPPUNIT_DIR} - $ENV{CPPUNIT_DIR} - /usr - /opt - PATH_SUFFIXES include -) - -find_library( CPPUNIT_LIBRARIES cppunit - HINTS - ${CPPUNIT_DIR} - $ENV{CPPUNIT_DIR} - /usr - /opt - PATH_SUFFIXES lib -) - -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) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 30775cca90f..435d07dbbbd 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -72,13 +72,11 @@ endif() if( ENABLE_TESTS ) if( FORCE_ENABLED ) - find_package( CppUnit REQUIRED ) find_package( GTest REQUIRED ) else() - find_package( CppUnit ) find_package( GTest ) endif() - if( CPPUNIT_FOUND AND GTEST_FOUND ) + if( GTEST_FOUND ) set( BUILD_TESTS TRUE ) else() set( BUILD_TESTS FALSE ) diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index c7bd4db2f37..881084282ce 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -10,7 +10,7 @@ component_status( MACAROONS BUILD_MACAROONS MACAROONS_FOUND AND JSON_FOUND AND 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( TESTS BUILD_TESTS GTEST_FOUND ) component_status( TPC BUILD_TPC CURL_FOUND ) component_status( VOMSXRD BUILD_VOMS VOMS_FOUND ) component_status( XRDCL ENABLE_XRDCL TRUE_VAR ) diff --git a/debian/control b/debian/control index f447fe51b9c..8eaf3f9371d 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,6 @@ Build-Depends: attr, cmake, libgtest-dev, - libcppunit-dev, libisal-dev, pkg-config, libfuse-dev [linux-any kfreebsd-any], diff --git a/xrootd.spec b/xrootd.spec index b07567e2707..0859febe4e6 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -47,11 +47,6 @@ Source1: %{url}/download/v%{compat_version}/%{name}-%{compat_version}.tar.gz %undefine __cmake3_in_source_build %endif -%if %{with tests} -# CppUnit crashes with LTO enabled -%global _lto_cflags %nil -%endif - %if %{?rhel}%{!?rhel:0} == 7 BuildRequires: cmake3 BuildRequires: %{devtoolset}-toolchain @@ -134,7 +129,6 @@ BuildRequires: openssl-devel %if %{with tests} BuildRequires: attr -BuildRequires: cppunit-devel BuildRequires: gtest-devel BuildRequires: openssl %endif From 8af03ca58b384b8788701df57587d4744b987dcf Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 16:16:33 +0100 Subject: [PATCH 670/773] [CI] Remove CppUnit as dependency from GitHub Actions --- .github/workflows/CI.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 00a9de9fab8..5599a5de7c1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -44,7 +44,6 @@ jobs: apk add \ bash \ cmake \ - cppunit-dev \ ceph-dev \ curl-dev \ fuse-dev \ @@ -249,7 +248,6 @@ jobs: clang \ davix-dev \ g++ \ - libcppunit-dev \ libcurl4-openssl-dev \ libfuse-dev \ libgtest-dev \ @@ -310,7 +308,7 @@ jobs: run: sudo sed -i -e "s/localhost/localhost $(hostname)/g" /etc/hosts - name: Install dependencies with Homebrew - run: brew install cppunit davix googletest isa-l + run: brew install davix googletest isa-l - name: Install Python dependencies with pip run: python3 -m pip install --upgrade pip setuptools wheel From 7a31797f9aa53ca826bb633ba4297cf6984e5c2e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 8 Feb 2024 16:21:48 +0100 Subject: [PATCH 671/773] Update documentation to remove references to CppUnit --- docs/INSTALL.md | 5 +---- docs/TESTING.md | 17 ++++++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index b594ebcee2c..653cec35df8 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -20,7 +20,7 @@ together with the dependencies required to enable them. | SciTokens | scitokens-cpp-devel | | systemd support | systemd-devel | | VOMS | voms-devel | -| Testing | cppunit-devel gtest-devel | +| Testing | 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 @@ -46,7 +46,6 @@ depending on the distribution: ```sh dnf install \ cmake \ - cppunit-devel \ curl-devel \ davix-devel \ diffutils \ @@ -90,7 +89,6 @@ apt install \ cmake \ davix-dev \ g++ \ - libcppunit-dev \ libcurl4-openssl-dev \ libfuse-dev \ libgtest-dev \ @@ -121,7 +119,6 @@ install dependencies as well when building XRootD from source: ```sh brew install \ cmake \ - cppunit \ curl \ davix \ gcc \ diff --git a/docs/TESTING.md b/docs/TESTING.md index 8bbdf98b08d..6b22cc96ffc 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -11,15 +11,14 @@ 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 +XRootD unit and integration tests are enabled via the CMake configuration option +`-DENABLE_TESTS=ON`. Unit and integration tests may depend on GoogleTest. +Therefore, the development packages for GoogleTest (i.e. `gtest-devel` or +equivalent) 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 From 01b16a2dc1acbc1b62c4432b280a15f1b5ad1b65 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 9 Feb 2024 13:39:09 +0100 Subject: [PATCH 672/773] [CMake] Enable XrdEc by default and use isa-l from the system --- cmake/XRootDDefaults.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index 4dd9af38f4d..ede87eec391 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -24,14 +24,14 @@ option( XRDCL_ONLY "Build only the client and necessary dependencies" 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 ) option( ENABLE_VOMS "Enable VOMS plug-in if possible." TRUE ) -option( ENABLE_XRDEC "Enable erasure coding component." FALSE ) +option( ENABLE_XRDEC "Enable erasure coding component." TRUE ) option( ENABLE_ASAN "Enable adress sanitizer." FALSE ) option( ENABLE_TSAN "Enable thread sanitizer." FALSE ) option( ENABLE_XRDCLHTTP "Enable xrdcl-http plugin." TRUE ) cmake_dependent_option( ENABLE_SCITOKENS "Enable SciTokens plugin." TRUE "NOT XRDCL_ONLY" FALSE ) cmake_dependent_option( ENABLE_MACAROONS "Enable Macaroons plugin." TRUE "NOT XRDCL_ONLY" FALSE ) option( FORCE_ENABLED "Fail build if enabled components cannot be built." FALSE ) -cmake_dependent_option( USE_SYSTEM_ISAL "Use isa-l installed in the system" FALSE "ENABLE_XRDEC" FALSE ) +cmake_dependent_option( USE_SYSTEM_ISAL "Use isa-l installed in the system" TRUE "ENABLE_XRDEC" FALSE ) define_default( XRD_PYTHON_REQ_VERSION 3 ) # backward compatibility From 44e233c10d501fef6389df722b01dfa01cbd0732 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 15 Feb 2024 13:51:39 +0100 Subject: [PATCH 673/773] [XrdCeph] Include instead of Fixes warning below: In file included from xrootd/src/XrdCeph/XrdCephPosix.cc:31: include/sys/errno.h:1:2: warning: #warning redirecting incorrect #include to [-Wcpp] --- 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 2e31e528774..d9c085e2500 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -28,7 +28,7 @@ #include #include -#include +#include #include #include #include From a5cfef7b28bbb31fb184077a455ad19aae77a3d1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 18 Feb 2024 12:24:42 +0100 Subject: [PATCH 674/773] [CMake] Add new option to allow disabling server tests In some cases, for example, when the build directory is on tmpfs and the kernel was built without support for extended attributes on tmpfs, the server tests would fail. This option is to allow one to disable the server tests in such cases. See issues #2096 and #2196. --- cmake/XRootDDefaults.cmake | 7 +++++++ cmake/XRootDSummary.cmake | 2 ++ tests/CMakeLists.txt | 5 +---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index ede87eec391..5d07603b37a 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -18,6 +18,7 @@ option( ENABLE_KRB5 "Enable the Kerberos 5 authentication if possible." option( ENABLE_READLINE "Enable the lib readline support in the commandline utilities." TRUE ) option( ENABLE_XRDCL "Enable XRootD client." TRUE ) option( ENABLE_TESTS "Enable unit tests." FALSE ) +cmake_dependent_option( ENABLE_SERVER_TESTS "Enable server tests." TRUE "ENABLE_TESTS" FALSE ) option( ENABLE_HTTP "Enable HTTP component." TRUE ) option( ENABLE_PYTHON "Enable python bindings." TRUE ) option( XRDCL_ONLY "Build only the client and necessary dependencies" FALSE ) @@ -38,3 +39,9 @@ define_default( XRD_PYTHON_REQ_VERSION 3 ) if(XRDCEPH_SUBMODULE) set(ENABLE_CEPH TRUE) endif() + +execute_process(COMMAND id -u OUTPUT_VARIABLE UID OUTPUT_STRIP_TRAILING_WHITESPACE) + +if(XRDCL_ONLY OR XRDCL_LIB_ONLY OR UID EQUAL 0) + set(ENABLE_SERVER_TESTS FALSE CACHE BOOL "Server not available or running as root" FORCE) +endif() diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index 881084282ce..3465556d833 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -10,6 +10,7 @@ component_status( MACAROONS BUILD_MACAROONS MACAROONS_FOUND AND JSON_FOUND AND 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( SERVER_TESTS ENABLE_SERVER_TESTS TRUE_VAR ) component_status( TESTS BUILD_TESTS GTEST_FOUND ) component_status( TPC BUILD_TPC CURL_FOUND ) component_status( VOMSXRD BUILD_VOMS VOMS_FOUND ) @@ -38,6 +39,7 @@ message( STATUS "Erasure coding: " ${STATUS_XRDEC} ) message( STATUS "Macaroons: " ${STATUS_MACAROONS} ) message( STATUS "SciTokens: " ${STATUS_SCITOKENS} ) message( STATUS "Tests: " ${STATUS_TESTS} ) +message( STATUS "Server Tests: " ${STATUS_SERVER_TESTS} ) message( STATUS "----------------------------------------" ) if( FORCE_ENABLED ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bb88cc32e26..3648cbfdb7e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,10 +14,7 @@ add_subdirectory(XrdHttpTests) add_subdirectory( XrdSsiTests ) -execute_process(COMMAND id -u OUTPUT_VARIABLE UID - OUTPUT_STRIP_TRAILING_WHITESPACE) - -if(XRDCL_ONLY OR UID EQUAL 0) +if(NOT ENABLE_SERVER_TESTS) return() endif() From 8bbc07f4f39072add91ecc2c97347c49f004703a Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 20 Feb 2024 15:07:27 +0100 Subject: [PATCH 675/773] [XrdTls] Restrict renegotiation for TLSv1.2 and earlier Fixes: #1689 --- src/XrdTls/XrdTlsContext.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 1d82795d2eb..d9ddf50a00e 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -591,8 +591,14 @@ XrdTlsContext::XrdTlsContext(const char *cert, const char *key, SSL_CTX **ctxLoc; } ctx_tracker(&pImpl->ctx); - static const int sslOpts = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 - | SSL_OP_NO_COMPRESSION; + static const int sslOpts = SSL_OP_ALL + | SSL_OP_NO_SSLv2 + | SSL_OP_NO_SSLv3 + | SSL_OP_NO_COMPRESSION +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + | SSL_OP_NO_RENEGOTIATION +#endif + ; std::string certFN, eText; const char *emsg; From fdb8c6746935fd3b34a157ff1944723511c29a8d Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 12 Mar 2024 18:35:18 -0700 Subject: [PATCH 676/773] [Server] Pass the kXR_seqio option all the way to the Oss plugin. --- src/XrdOfs/XrdOfs.cc | 8 ++++++++ src/XrdSfs/XrdSfsInterface.hh | 1 + src/XrdXrootd/XrdXrootdXeq.cc | 1 + 3 files changed, 10 insertions(+) diff --git a/src/XrdOfs/XrdOfs.cc b/src/XrdOfs/XrdOfs.cc index 6d829977344..8d91eb7e033 100644 --- a/src/XrdOfs/XrdOfs.cc +++ b/src/XrdOfs/XrdOfs.cc @@ -465,6 +465,7 @@ int XrdOfsFile::open(const char *path, // In SFS_O_CREAT - Create the file open in RW mode SFS_O_TRUNC - Trunc the file open in RW mode SFS_O_POSC - Presist file on successful close + SFS_O_SEQIO - Primarily sequential I/O (e.g. xrdcp) 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--"). Additionally, Mode @@ -723,6 +724,13 @@ int XrdOfsFile::open(const char *path, // In error.getUCap() & XrdOucEI::uLclF) open_flag |= O_DIRECT; } +// Pass across the sequential I/O hint. We don't support that for MacOS because +// the open flag we co-opt does not exist there and MacOS can't do it anyway. +// +#ifndef __APPLE__ + if (open_mode & SFS_O_SEQIO) open_flag |= O_RSYNC; +#endif + // Open the file // if ((retc = oP.fP->Open(path, open_flag, theMode, Open_Env))) diff --git a/src/XrdSfs/XrdSfsInterface.hh b/src/XrdSfs/XrdSfsInterface.hh index c0ec1cb9a5d..a94f837844d 100644 --- a/src/XrdSfs/XrdSfsInterface.hh +++ b/src/XrdSfs/XrdSfsInterface.hh @@ -63,6 +63,7 @@ #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 +#define SFS_O_SEQIO 0x10000000 // Open for sequential I/O // 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! diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index 5efe8df9162..83a9341c782 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -1438,6 +1438,7 @@ int XrdXrootdProtocol::do_Open() } if (opts & kXR_retstat) {*op++ = 't'; retStat = 1;} if (opts & kXR_posc) {*op++ = 'p'; openopts |= SFS_O_POSC;} + if (opts & kXR_seqio) {*op++ = 'S'; openopts |= SFS_O_SEQIO;} *op = '\0'; TRACEP(FS, "open " < Date: Wed, 13 Mar 2024 17:33:07 -0700 Subject: [PATCH 677/773] [VOMS] Allow VOMS config to use set variables. Fixes #2200 --- src/XrdVoms/XrdVomsMapfile.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XrdVoms/XrdVomsMapfile.cc b/src/XrdVoms/XrdVomsMapfile.cc index a93ad71e46b..dc881cd0be5 100644 --- a/src/XrdVoms/XrdVomsMapfile.cc +++ b/src/XrdVoms/XrdVomsMapfile.cc @@ -324,7 +324,8 @@ XrdVomsMapfile::Configure(XrdSysError *erp) if (!XrdOucEnv::Import("XRDCONFIGFN", config_filename)) { return VOMS_MAP_FAILED; } - XrdOucStream stream(erp, getenv("XRDINSTANCE")); + XrdOucEnv myEnv; + XrdOucStream stream(erp, getenv("XRDINSTANCE"), &myEnv, "=====> "); int cfg_fd; if ((cfg_fd = open(config_filename, O_RDONLY, 0)) < 0) { From 912b908e02500e2e5c953c08ccc038f3bea69ac1 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 13 Mar 2024 23:49:59 -0700 Subject: [PATCH 678/773] [Server] Check kXR_seqio to an opt-in strategy. --- src/XrdOfs/XrdOfs.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/XrdOfs/XrdOfs.cc b/src/XrdOfs/XrdOfs.cc index 8d91eb7e033..86e87b7fe4e 100644 --- a/src/XrdOfs/XrdOfs.cc +++ b/src/XrdOfs/XrdOfs.cc @@ -724,10 +724,10 @@ int XrdOfsFile::open(const char *path, // In error.getUCap() & XrdOucEI::uLclF) open_flag |= O_DIRECT; } -// Pass across the sequential I/O hint. We don't support that for MacOS because -// the open flag we co-opt does not exist there and MacOS can't do it anyway. +// Pass across the sequential I/O hint. We only support that for Linux because +// the open flag we co-opted does not exist elsewhere and those can't do much. // -#ifndef __APPLE__ +#ifdef __linux__ if (open_mode & SFS_O_SEQIO) open_flag |= O_RSYNC; #endif From b440a9e0cb653573212fa5905fdbbe7e828f466e Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Thu, 29 Feb 2024 22:15:02 -0600 Subject: [PATCH 679/773] Add monitoring packet for IO, based on the throttle plugin Given the throttle plugin is collecting timing statistics for I/O anyway, have it begin to report these statistics out through the g-stream. This allows the monitoring programs to determine the overall time spent in the I/O subsystem. --- src/XrdThrottle/XrdThrottle.hh | 7 ++-- .../XrdThrottleFileSystemConfig.cc | 32 ++++++++++++--- src/XrdThrottle/XrdThrottleManager.cc | 39 ++++++++++++++----- src/XrdThrottle/XrdThrottleManager.hh | 12 +++++- src/XrdXrootd/XrdXrootdConfigMon.cc | 37 ++++++++++-------- src/XrdXrootd/XrdXrootdMonData.hh | 1 + src/XrdXrootd/XrdXrootdMonitor.hh | 3 +- 7 files changed, 94 insertions(+), 37 deletions(-) diff --git a/src/XrdThrottle/XrdThrottle.hh b/src/XrdThrottle/XrdThrottle.hh index 9ddc890f373..cccd21229cd 100644 --- a/src/XrdThrottle/XrdThrottle.hh +++ b/src/XrdThrottle/XrdThrottle.hh @@ -135,7 +135,7 @@ private: class FileSystem : public XrdSfsFileSystem { -friend XrdSfsFileSystem * XrdSfsGetFileSystem_Internal(XrdSfsFileSystem *, XrdSysLogger *, const char *); +friend XrdSfsFileSystem * XrdSfsGetFileSystem_Internal(XrdSfsFileSystem *, XrdSysLogger *, const char *, XrdOucEnv *); public: @@ -260,14 +260,15 @@ public: const char *opaque = 0); virtual int - Configure(XrdSysError &, XrdSfsFileSystem *native_fs); + Configure(XrdSysError &, XrdSfsFileSystem *native_fs, XrdOucEnv *envP); private: static void Initialize( FileSystem *&fs, XrdSfsFileSystem *native_fs, XrdSysLogger *lp, - const char *config_file); + const char *config_file, + XrdOucEnv *envP); FileSystem(); diff --git a/src/XrdThrottle/XrdThrottleFileSystemConfig.cc b/src/XrdThrottle/XrdThrottleFileSystemConfig.cc index 5d1ef9f3bda..ad1cfbd802a 100644 --- a/src/XrdThrottle/XrdThrottleFileSystemConfig.cc +++ b/src/XrdThrottle/XrdThrottleFileSystemConfig.cc @@ -55,10 +55,11 @@ namespace XrdThrottle { XrdSfsFileSystem * XrdSfsGetFileSystem_Internal(XrdSfsFileSystem *native_fs, XrdSysLogger *lp, - const char *configfn) + const char *configfn, + XrdOucEnv *envP) { FileSystem* fs = NULL; - FileSystem::Initialize(fs, native_fs, lp, configfn); + FileSystem::Initialize(fs, native_fs, lp, configfn, envP); return fs; } } @@ -70,11 +71,21 @@ XrdSfsGetFileSystem(XrdSfsFileSystem *native_fs, XrdSysLogger *lp, const char *configfn) { - return XrdSfsGetFileSystem_Internal(native_fs, lp, configfn); + return XrdSfsGetFileSystem_Internal(native_fs, lp, configfn, nullptr); +} + +XrdSfsFileSystem * +XrdSfsGetFileSystem2(XrdSfsFileSystem *native_fs, + XrdSysLogger *lp, + const char *configfn, + XrdOucEnv *envP) +{ + return XrdSfsGetFileSystem_Internal(native_fs, lp, configfn, envP); } } XrdVERSIONINFO(XrdSfsGetFileSystem, FileSystem); +XrdVERSIONINFO(XrdSfsGetFileSystem2, FileSystem); FileSystem* FileSystem::m_instance = 0; @@ -90,7 +101,8 @@ void FileSystem::Initialize(FileSystem *&fs, XrdSfsFileSystem *native_fs, XrdSysLogger *lp, - const char *configfn) + const char *configfn, + XrdOucEnv *envP) { fs = NULL; if (m_instance == NULL && !(m_instance = new FileSystem())) @@ -103,7 +115,7 @@ FileSystem::Initialize(FileSystem *&fs, 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)) + if (fs->Configure(fs->m_eroute, native_fs, envP)) { fs->m_eroute.Say("Initialization of throttled file system failed."); fs = NULL; @@ -116,7 +128,7 @@ FileSystem::Initialize(FileSystem *&fs, #define TS_Xeq(key, func) NoGo = (strcmp(key, var) == 0) ? func(Config) : 0 int -FileSystem::Configure(XrdSysError & log, XrdSfsFileSystem *native_fs) +FileSystem::Configure(XrdSysError & log, XrdSfsFileSystem *native_fs, XrdOucEnv *envP) { XrdOucEnv myEnv; XrdOucStream Config(&m_eroute, getenv("XRDINSTANCE"), &myEnv, "(Throttle Config)> "); @@ -165,6 +177,14 @@ FileSystem::Configure(XrdSysError & log, XrdSfsFileSystem *native_fs) // Overwrite the environment variable saying that throttling is the fslib. XrdOucEnv::Export("XRDOFSLIB", fslib.c_str()); + if (envP) + { + auto gstream = reinterpret_cast(envP->GetPtr("Throttle.gStream*")); + log.Say("Config", "Throttle g-stream has", gstream ? "" : " NOT", " been configured via xrootd.mongstream directive"); + m_throttle.SetMonitor(gstream); + } + + return 0; } diff --git a/src/XrdThrottle/XrdThrottleManager.cc b/src/XrdThrottle/XrdThrottleManager.cc index 0bca3f1fc4d..651bd6d9baf 100644 --- a/src/XrdThrottle/XrdThrottleManager.cc +++ b/src/XrdThrottle/XrdThrottleManager.cc @@ -1,11 +1,11 @@ #include "XrdThrottleManager.hh" +#include "XrdOuc/XrdOucEnv.hh" #include "XrdSys/XrdSysAtomics.hh" #include "XrdSys/XrdSysTimer.hh" #include "XrdSys/XrdSysPthread.hh" - -#include "XrdOuc/XrdOucEnv.hh" +#include "XrdXrootd/XrdXrootdGStream.hh" #define XRD_TRACE m_trace-> #include "XrdThrottle/XrdThrottleTrace.hh" @@ -33,7 +33,7 @@ XrdThrottleManager::XrdThrottleManager(XrdSysError *lP, XrdOucTrace *tP) : m_ops_per_second(-1), m_concurrency_limit(-1), m_last_round_allocation(100*1024), - m_io_counter(0), + m_io_active(0), m_loadshed_host(""), m_loadshed_port(0), m_loadshed_frequency(0), @@ -430,7 +430,10 @@ XrdThrottleManager::RecomputeInternal() // Update the IO counters m_compute_var.Lock(); - m_stable_io_counter = AtomicGet(m_io_counter); + m_stable_io_active = AtomicGet(m_io_active); + auto io_active = m_stable_io_active; + m_stable_io_total = static_cast(AtomicGet(m_io_total)); + auto io_total = m_stable_io_total; time_t secs; AtomicFZAP(secs, m_io_wait.tv_sec); long nsecs; AtomicFZAP(nsecs, m_io_wait.tv_nsec); m_stable_io_wait.tv_sec += static_cast(secs * intervals_per_second); @@ -440,8 +443,25 @@ XrdThrottleManager::RecomputeInternal() m_stable_io_wait.tv_nsec -= 1000000000; m_stable_io_wait.tv_nsec --; } + struct timespec io_wait_ts; + io_wait_ts.tv_sec = m_stable_io_wait.tv_sec; + io_wait_ts.tv_nsec = 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."); + uint64_t io_wait_ms = io_wait_ts.tv_sec*1000+io_wait_ts.tv_nsec/1000000; + TRACE(IOLOAD, "Current IO counter is " << io_active << "; total IO wait time is " << io_wait_ms << "ms."); + if (m_gstream) + { + char buf[128]; + auto len = snprintf(buf, 128, + R"({"event":"throttle_update","io_wait":%.4f,"io_active":%d,"io_total":%d})", + static_cast(io_wait_ms) / 1000.0, io_active, io_total); + auto suc = (len < 128) ? m_gstream->Insert(buf, len + 1) : false; + if (!suc) + { + TRACE(IOLOAD, "Failed g-stream insertion of throttle_update record (len=" << len << "): " << buf); + } + } m_compute_var.Broadcast(); } @@ -470,17 +490,18 @@ XrdThrottleTimer XrdThrottleManager::StartIOTimer() { AtomicBeg(m_compute_var); - int cur_counter = AtomicInc(m_io_counter); + int cur_counter = AtomicInc(m_io_active); + AtomicInc(m_io_total); 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); + AtomicDec(m_io_active); AtomicEnd(m_compute_var); m_compute_var.Wait(); AtomicBeg(m_compute_var); - cur_counter = AtomicInc(m_io_counter); + cur_counter = AtomicInc(m_io_active); AtomicEnd(m_compute_var); } return XrdThrottleTimer(*this); @@ -493,7 +514,7 @@ void XrdThrottleManager::StopIOTimer(struct timespec timer) { AtomicBeg(m_compute_var); - AtomicDec(m_io_counter); + AtomicDec(m_io_active); 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); diff --git a/src/XrdThrottle/XrdThrottleManager.hh b/src/XrdThrottle/XrdThrottleManager.hh index dd1dc2e7a8a..6f292ac7c82 100644 --- a/src/XrdThrottle/XrdThrottleManager.hh +++ b/src/XrdThrottle/XrdThrottleManager.hh @@ -40,6 +40,7 @@ class XrdSysError; class XrdOucTrace; class XrdThrottleTimer; +class XrdXrootdGStream; class XrdThrottleManager { @@ -68,6 +69,8 @@ void SetMaxOpen(unsigned long max_open) {m_max_open = max_open;} void SetMaxConns(unsigned long max_conns) {m_max_conns = max_conns;} +void SetMonitor(XrdXrootdGStream *gstream) {m_gstream = gstream;} + //int Stats(char *buff, int blen, int do_sync=0) {return m_pool.Stats(buff, blen, do_sync);} static @@ -125,10 +128,12 @@ std::vector m_secondary_ops_shares; int m_last_round_allocation; // Active IO counter -int m_io_counter; +int m_io_active; struct timespec m_io_wait; +unsigned m_io_total{0}; // Stable IO counters - must hold m_compute_var lock when reading/writing; -int m_stable_io_counter; +int m_stable_io_active; +int m_stable_io_total{0}; // It would take ~3 years to overflow a 32-bit unsigned integer at 100Hz of IO operations. struct timespec m_stable_io_wait; // Load shed details @@ -145,6 +150,9 @@ std::unordered_map m_conn_counters; std::unordered_map>> m_active_conns; std::mutex m_file_mutex; +// Monitoring handle, if configured +XrdXrootdGStream* m_gstream{nullptr}; + static const char *TraceID; }; diff --git a/src/XrdXrootd/XrdXrootdConfigMon.cc b/src/XrdXrootd/XrdXrootdConfigMon.cc index d8d48ce64b8..3acc2e150d5 100644 --- a/src/XrdXrootd/XrdXrootdConfigMon.cc +++ b/src/XrdXrootd/XrdXrootdConfigMon.cc @@ -80,14 +80,16 @@ struct MonParms MonParms *MP = 0; struct XrdXrootdGSReal::GSParms gsObj[] = - {{"ccm", 0, XROOTD_MON_CCM, 0, -1, XROOTD_MON_GSCCM, 0, - XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm}, - {"pfc", 0, XROOTD_MON_PFC, 0, -1, XROOTD_MON_GSPFC, 0, - XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm}, - {"TcpMon", 0, XROOTD_MON_TCPMO, 0, -1, XROOTD_MON_GSTCP, 0, - XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm}, - {"Tpc", 0, XROOTD_MON_TPC, 0, -1, XROOTD_MON_GSTPC, 0, - XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm} + {{"ccm", 0, XROOTD_MON_CCM, 0, -1, XROOTD_MON_GSCCM, 0, + XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm}, + {"pfc", 0, XROOTD_MON_PFC, 0, -1, XROOTD_MON_GSPFC, 0, + XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm}, + {"TcpMon", 0, XROOTD_MON_TCPMO, 0, -1, XROOTD_MON_GSTCP, 0, + XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm}, + {"Throttle", 0, XROOTD_MON_THROT, 0, -1, XROOTD_MON_GSTHR, 0, + XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm}, + {"Tpc", 0, XROOTD_MON_TPC, 0, -1, XROOTD_MON_GSTPC, 0, + XrdXrootdGSReal::fmtBin, XrdXrootdGSReal::hdrNorm} }; } @@ -100,7 +102,7 @@ bool XrdXrootdProtocol::ConfigGStream(XrdOucEnv &myEnv, XrdOucEnv *urEnv) XrdXrootdGStream *gs; static const int numgs=sizeof(gsObj)/sizeof(struct XrdXrootdGSReal::GSParms); char vbuff[64]; - bool aOK, gXrd[numgs] = {false, false, true, true}; + bool aOK, gXrd[numgs] = {false, false, true, false, true}; // For each enabled monitoring provider, allocate a g-stream and put // its address in our environment. @@ -190,7 +192,7 @@ bool XrdXrootdProtocol::ConfigMon(XrdProtocol_Config *pi, XrdOucEnv &xrootdEnv) [rbuff ] [rnums ] [window ] [dest [Events] ] - Events: [ccm] [files] [fstat] [info] [io] [iov] [pfc] [redir] [tcpmon] [user] + Events: [ccm] [files] [fstat] [info] [io] [iov] [pfc] [redir] [tcpmon] [throttle] [user] all enables monitoring for all connections. auth add authentication information to "user". @@ -222,6 +224,7 @@ bool XrdXrootdProtocol::ConfigMon(XrdProtocol_Config *pi, XrdOucEnv &xrootdEnv) pfc monitor proxy file cache redir monitors request redirections tcpmon monitors tcp connection closes. + throttle monitors I/O activity via the throttle plugin tpc Third Party Copy user monitors user login and disconnect events. where monitor records are to be sentvia UDP. @@ -350,11 +353,12 @@ int XrdXrootdProtocol::xmon(XrdOucStream &Config) else if (!strcmp("io", val)) MP->monMode[i] |= XROOTD_MON_IO; else if (!strcmp("iov", val)) MP->monMode[i] |= (XROOTD_MON_IO |XROOTD_MON_IOV); - else if (!strcmp("pfc", val)) MP->monMode[i] |= XROOTD_MON_PFC; - else if (!strcmp("redir",val)) MP->monMode[i] |= XROOTD_MON_REDR; - else if (!strcmp("tcpmon",val))MP->monMode[i] |= XROOTD_MON_TCPMO; - else if (!strcmp("tpc", val))MP->monMode[i] |= XROOTD_MON_TPC; - else if (!strcmp("user", val)) MP->monMode[i] |= XROOTD_MON_USER; + else if (!strcmp("pfc", val)) MP->monMode[i] |= XROOTD_MON_PFC; + else if (!strcmp("redir", val)) MP->monMode[i] |= XROOTD_MON_REDR; + else if (!strcmp("tcpmon", val)) MP->monMode[i] |= XROOTD_MON_TCPMO; + else if (!strcmp("throttle", val)) MP->monMode[i] |= XROOTD_MON_THROT; + else if (!strcmp("tpc", val)) MP->monMode[i] |= XROOTD_MON_TPC; + else if (!strcmp("user", val)) MP->monMode[i] |= XROOTD_MON_USER; else break; if (!val) {eDest.Emsg("Config","monitor dest value not specified"); @@ -456,6 +460,7 @@ char *XrdXrootdProtocol::xmondest(const char *what, char *val) ccm gstream: cache context management pfc gstream: proxy file cache tcpmon gstream: tcp connection monitoring + throttle gstream: monitors I/O activity via the throttle plugin tpc gstream: Third Party Copy noXXX do not include information. @@ -478,7 +483,7 @@ int XrdXrootdProtocol::xmongs(XrdOucStream &Config) int numgs = sizeof(gsObj)/sizeof(struct XrdXrootdGSReal::GSParms); int selAll = XROOTD_MON_CCM | XROOTD_MON_PFC | XROOTD_MON_TCPMO - | XROOTD_MON_TPC; + | XROOTD_MON_THROT | XROOTD_MON_TPC; int i, selMon = 0, opt = -1, hdr = -1, fmt = -1, flushVal = -1; long long maxlVal = -1; char *val, *dest = 0; diff --git a/src/XrdXrootd/XrdXrootdMonData.hh b/src/XrdXrootd/XrdXrootdMonData.hh index 1c98a6b4707..82035e7cb17 100644 --- a/src/XrdXrootd/XrdXrootdMonData.hh +++ b/src/XrdXrootd/XrdXrootdMonData.hh @@ -119,6 +119,7 @@ const kXR_char XROOTD_MON_GSCCM = 'M'; // pfc: Cache context mgt info const kXR_char XROOTD_MON_GSPFC = 'C'; // pfc: Cache monitoring info const kXR_char XROOTD_MON_GSTCP = 'T'; // TCP connection statistics const kXR_char XROOTD_MON_GSTPC = 'P'; // TPC Third Party Copy +const kXR_char XROOTD_MON_GSTHR = 'R'; // IO activity from the throttle plugin // 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. diff --git a/src/XrdXrootd/XrdXrootdMonitor.hh b/src/XrdXrootd/XrdXrootdMonitor.hh index 1c1c142ad63..52205e1825f 100644 --- a/src/XrdXrootd/XrdXrootdMonitor.hh +++ b/src/XrdXrootd/XrdXrootdMonitor.hh @@ -59,7 +59,8 @@ #define XROOTD_MON_PFC 0x00000400 #define XROOTD_MON_TCPMO 0x00000800 #define XROOTD_MON_TPC 0x00001000 -#define XROOTD_MON_GSTRM (XROOTD_MON_CCM | XROOTD_MON_PFC | XROOTD_MON_TCPMO) +#define XROOTD_MON_THROT 0x00002000 +#define XROOTD_MON_GSTRM (XROOTD_MON_CCM | XROOTD_MON_PFC | XROOTD_MON_TCPMO | XROOTD_MON_THROT) #define XROOTD_MON_FSLFN 1 #define XROOTD_MON_FSOPS 2 From a78586f01afc2b4536bd7d394ef13e1f07a3c341 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Sat, 16 Mar 2024 16:03:17 -0700 Subject: [PATCH 680/773] [Server] Implement the kXR_seqio open option for sequential I/O. --- src/XrdOfs/XrdOfs.cc | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/XrdOfs/XrdOfs.cc b/src/XrdOfs/XrdOfs.cc index 86e87b7fe4e..ec1dd314046 100644 --- a/src/XrdOfs/XrdOfs.cc +++ b/src/XrdOfs/XrdOfs.cc @@ -724,13 +724,6 @@ int XrdOfsFile::open(const char *path, // In error.getUCap() & XrdOucEI::uLclF) open_flag |= O_DIRECT; } -// Pass across the sequential I/O hint. We only support that for Linux because -// the open flag we co-opted does not exist elsewhere and those can't do much. -// -#ifdef __linux__ - if (open_mode & SFS_O_SEQIO) open_flag |= O_RSYNC; -#endif - // Open the file // if ((retc = oP.fP->Open(path, open_flag, theMode, Open_Env))) @@ -766,6 +759,15 @@ int XrdOfsFile::open(const char *path, // In oP.hP->Activate(oP.fP); oP.hP->UnLock(); +// If this is being opened for sequential I/O advise the filesystem about it. +// +#ifdef __linux__ + if (!(XrdOfsFS->OssIsProxy) && open_mode & SFS_O_SEQIO) + {int theFD = oP.fP->getFD(); + if (theFD >= 0) posix_fadvise(theFD, 0, 0, POSIX_FADV_SEQUENTIAL); + } +#endif + // Send an open event if we must // if (XrdOfsFS->evsObject) From 432cc122cc65275cd41e664f9602ea3f320675b6 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Sat, 16 Mar 2024 16:26:29 -0700 Subject: [PATCH 681/773] [Server] Harden kXR_seqio implementation. --- src/XrdOfs/XrdOfs.cc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/XrdOfs/XrdOfs.cc b/src/XrdOfs/XrdOfs.cc index ec1dd314046..cb8d89cdaf0 100644 --- a/src/XrdOfs/XrdOfs.cc +++ b/src/XrdOfs/XrdOfs.cc @@ -74,6 +74,7 @@ #include "XrdSys/XrdSysLogger.hh" #include "XrdSys/XrdSysPlatform.hh" #include "XrdSys/XrdSysPthread.hh" +#include "XrdSys/XrdSysRAtomic.hh" #include "XrdOuc/XrdOuca2x.hh" #include "XrdOuc/XrdOucEnv.hh" @@ -761,10 +762,15 @@ int XrdOfsFile::open(const char *path, // In // If this is being opened for sequential I/O advise the filesystem about it. // -#ifdef __linux__ +#if defined(__linux__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) if (!(XrdOfsFS->OssIsProxy) && open_mode & SFS_O_SEQIO) - {int theFD = oP.fP->getFD(); - if (theFD >= 0) posix_fadvise(theFD, 0, 0, POSIX_FADV_SEQUENTIAL); + {static RAtomic_int fadFails(0); + int theFD = oP.fP->getFD(); + if (theFD >= 0 && fadFails < 4096) + if (posix_fadvise(theFD, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) + {OfsEroute.Emsg(epname, errno, "fadsize for sequential I/O."); + fadFails++; + } } #endif From eded1bbea58c487f39824cf2af521ec63a86e352 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sat, 16 Mar 2024 21:43:38 +0100 Subject: [PATCH 682/773] Fixes for 64 bit time_t on 32 bit systems --- src/XrdApps/Xrdadler32.cc | 2 +- src/XrdBwm/XrdBwmLogger.cc | 5 +++-- src/XrdHttp/XrdHttpReq.cc | 2 +- src/XrdPfc/XrdPfcConfiguration.cc | 2 +- src/XrdPosix/XrdPosixPreload32.cc | 4 ++++ src/XrdSecsss/XrdSecsssKT.cc | 5 +++-- src/XrdXrootd/XrdXrootdPrepare.cc | 3 ++- src/XrdXrootd/XrdXrootdProtocol.cc | 9 +++++---- 8 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/XrdApps/Xrdadler32.cc b/src/XrdApps/Xrdadler32.cc index 4c4e0068b9e..ab333656ec1 100644 --- a/src/XrdApps/Xrdadler32.cc +++ b/src/XrdApps/Xrdadler32.cc @@ -88,7 +88,7 @@ int fGetXattrAdler32(int fd, const char* attr, char *value) int rc; if (fstat(fd, &st)) return 0; - sprintf(mtime, "%ld", st.st_mtime); + sprintf(mtime, "%lld", (long long) st.st_mtime); #if defined(__linux__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) rc = fgetxattr(fd, attr, attr_val, 25); diff --git a/src/XrdBwm/XrdBwmLogger.cc b/src/XrdBwm/XrdBwmLogger.cc index 0064f8b7e67..ee0788396f8 100644 --- a/src/XrdBwm/XrdBwmLogger.cc +++ b/src/XrdBwm/XrdBwmLogger.cc @@ -146,11 +146,12 @@ void XrdBwmLogger::Event(Info &eInfo) tp->Tlen = snprintf(tp->Text, XrdBwmLoggerMsg::msgSize, "%s%s" "%s%s%c" - "%ld%ld%ld" + "%lld%lld%lld" "%d%d%d" "%lld%d%c", eInfo.Tident, eInfo.Lfn, eInfo.lclNode, eInfo.rmtNode, - eInfo.Flow, eInfo.ATime, eInfo.BTime, eInfo.CTime, + eInfo.Flow, (long long) eInfo.ATime, + (long long) eInfo.BTime, (long long) eInfo.CTime, eInfo.numqIn, eInfo.numqOut, eInfo.numqXeq, eInfo.Size, eInfo.ESec, theEOL); diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 9a54293b52f..b61587118aa 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -656,7 +656,7 @@ void XrdHttpReq::appendOpaque(XrdOucString &s, XrdSecEntity *secent, char *hash, s += "&xrdhttptime="; char buf[256]; - sprintf(buf, "%ld", tnow); + sprintf(buf, "%lld", (long long) tnow); s += buf; if (secent) { diff --git a/src/XrdPfc/XrdPfcConfiguration.cc b/src/XrdPfc/XrdPfcConfiguration.cc index 8f4bcd064ed..2cdb5869394 100644 --- a/src/XrdPfc/XrdPfcConfiguration.cc +++ b/src/XrdPfc/XrdPfcConfiguration.cc @@ -477,7 +477,7 @@ bool Cache::Config(const char *config_filename, const char *parameters) if (m_configuration.m_cs_UVKeep < 0) strcpy(uvk, "lru"); else - sprintf(uvk, "%ld", m_configuration.m_cs_UVKeep); + sprintf(uvk, "%lld", (long long) m_configuration.m_cs_UVKeep); float rg = (m_configuration.m_RamAbsAvailable) / float(1024*1024*1024); loff = snprintf(buff, sizeof(buff), "Config effective %s pfc configuration:\n" " pfc.cschk %s uvkeep %s\n" diff --git a/src/XrdPosix/XrdPosixPreload32.cc b/src/XrdPosix/XrdPosixPreload32.cc index 3d99ba9d2ee..28c0cacef75 100644 --- a/src/XrdPosix/XrdPosixPreload32.cc +++ b/src/XrdPosix/XrdPosixPreload32.cc @@ -44,6 +44,10 @@ #ifdef _FILE_OFFSET_BITS #undef _FILE_OFFSET_BITS #endif + +#ifdef _TIME_BITS +#undef _TIME_BITS +#endif #endif #define XRDPOSIXPRELOAD32 diff --git a/src/XrdSecsss/XrdSecsssKT.cc b/src/XrdSecsss/XrdSecsssKT.cc index 38b1ada15ab..0071b20f1dc 100644 --- a/src/XrdSecsss/XrdSecsssKT.cc +++ b/src/XrdSecsss/XrdSecsssKT.cc @@ -371,10 +371,11 @@ int XrdSecsssKT::Rewrite(int Keep, int &numKeys, int &numTot, int &numExp) 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:", + n = sprintf(buff, "%s0 u:%s g:%s n:%s N:%lld c:%lld e:%lld 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); + (long long) ktP->Data.Crt, (long long) 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; diff --git a/src/XrdXrootd/XrdXrootdPrepare.cc b/src/XrdXrootd/XrdXrootdPrepare.cc index 8ce494c0762..94700936228 100644 --- a/src/XrdXrootd/XrdXrootdPrepare.cc +++ b/src/XrdXrootd/XrdXrootdPrepare.cc @@ -129,7 +129,8 @@ int XrdXrootdPrepare::List(XrdXrootdPrepArgs &pargs, char *resp, int resplen) 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); + return snprintf(resp, resplen-1, "%s %lld", + dp->d_name, (long long) buf.st_mtime); } // Completed diff --git a/src/XrdXrootd/XrdXrootdProtocol.cc b/src/XrdXrootd/XrdXrootdProtocol.cc index e0e50e75d7d..ec98d7d1ed2 100644 --- a/src/XrdXrootd/XrdXrootdProtocol.cc +++ b/src/XrdXrootd/XrdXrootdProtocol.cc @@ -798,8 +798,8 @@ int XrdXrootdProtocol::StatGen(struct stat &buf, char *xxBuff, int xxLen, // Format the default response: // - m = snprintf(xxBuff, xxLen, "%lld %lld %d %ld", - Dev.uuid, fsz, flags, buf.st_mtime); + m = snprintf(xxBuff, xxLen, "%lld %lld %d %lld", + Dev.uuid, fsz, flags, (long long) buf.st_mtime); // if (!xtnd || m >= xxLen) return xxLen; // @@ -808,8 +808,9 @@ int XrdXrootdProtocol::StatGen(struct stat &buf, char *xxBuff, int xxLen, char *origP = xxBuff; char *nullP = xxBuff + m++; xxBuff += m; xxLen -= m; - n = snprintf(xxBuff, xxLen, "%ld %ld %04o ", - buf.st_ctime, buf.st_atime, buf.st_mode&07777); + n = snprintf(xxBuff, xxLen, "%lld %lld %04o ", + (long long) buf.st_ctime, (long long) buf.st_atime, + buf.st_mode&07777); if (n >= xxLen) return m; xxBuff += n; xxLen -= n; From 5ca591165a511ca8b3d34943cceb503d44b41f18 Mon Sep 17 00:00:00 2001 From: Christopher Larrieu Date: Tue, 19 Mar 2024 09:50:51 +0100 Subject: [PATCH 683/773] [XrdOss] Fix check for option noDread in XrdOssDir::Readdir() Need to check if bit for noDread is enabled in dOpts for the check to work correctly. Signed-off-by: Guilherme Amadio --- src/XrdOss/XrdOssApi.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdOss/XrdOssApi.cc b/src/XrdOss/XrdOssApi.cc index 5e453494f55..9a6bdd7dd53 100644 --- a/src/XrdOss/XrdOssApi.cc +++ b/src/XrdOss/XrdOssApi.cc @@ -609,7 +609,7 @@ int XrdOssDir::Readdir(char *buff, int blen) // Simulate the read operation, if need be. // - if (noDread) + if (dOpts & noDread) {if (ateof) *buff = '\0'; else {*buff = '.'; ateof = true;} return XrdOssOK; From 75d1af2aa71373b2b5a215f5723a425719bf1e05 Mon Sep 17 00:00:00 2001 From: Christopher Larrieu Date: Tue, 19 Mar 2024 09:54:29 +0100 Subject: [PATCH 684/773] [XrdOss] Do not set S_IFBLK in XrdOssSys::Stat() Setting this on all paths causes directories to appear as files when using oss.rsscmd option. Signed-off-by: Guilherme Amadio --- src/XrdOss/XrdOssStat.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/XrdOss/XrdOssStat.cc b/src/XrdOss/XrdOssStat.cc index 562a69f3991..67efd6ebef7 100644 --- a/src/XrdOss/XrdOssStat.cc +++ b/src/XrdOss/XrdOssStat.cc @@ -123,7 +123,6 @@ int XrdOssSys::Stat(const char *path, struct stat *buff, int opts, // if ((retc = MSS_Stat(remote_path, buff))) return retc; if (popts & XRDEXP_NOTRW) buff->st_mode &= ro_Mode; - buff->st_mode |= S_IFBLK; return XrdOssOK; } From 77a335d2ce1a9aa7bd0e7365656192d6e74fa143 Mon Sep 17 00:00:00 2001 From: Elvin Sindrilaru Date: Tue, 19 Mar 2024 10:47:08 +0100 Subject: [PATCH 685/773] [XrdTpc] Force HTTP 1.1 for TPC otherwise transfers fail In Alma 9.3 the default curl version is 7.76.1 and according to the documentation [1], starting with 7.62.0 the default HTTP protocol used is HTTP 2 TLS. Since XRootD supports only HTTP 1.1, this leads to the following error whenever a HTTP TPC transfer is attempted: "HTTP library failure: Stream error in the HTTP/2 framing layer" This patch will force curl to use HTTP 1.1 without even attempting HTTP 2. This is a workaround for the currently available curl version since normaly curl should be able to gracefully downgrade from HTTP 2 to 1.1 by itself. [1] https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html --- src/XrdTpc/XrdTpcTPC.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 49f7dd4477f..7869d236652 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -873,6 +873,7 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); } curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1); // curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_setcloexec_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec); @@ -987,6 +988,7 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) curl_easy_setopt(curl, CURLOPT_INTERFACE, ip); } curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1); // curl_easy_setopt(curl,CURLOPT_SOCKOPTFUNCTION,sockopt_setcloexec_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec); From 5fda7a9154f939f979241b01c7f6179f85a2f6a1 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 19 Mar 2024 17:07:28 -0700 Subject: [PATCH 686/773] [Ouc] Extend XrdOucGatherConf to do more boiler plate work and be extendable. --- src/XrdOuc/XrdOucGatherConf.cc | 264 ++++++++++++++++++++++++++++++--- src/XrdOuc/XrdOucGatherConf.hh | 130 ++++++++++++++-- 2 files changed, 359 insertions(+), 35 deletions(-) diff --git a/src/XrdOuc/XrdOucGatherConf.cc b/src/XrdOuc/XrdOucGatherConf.cc index cef0c6c2c53..0d0235d9f99 100644 --- a/src/XrdOuc/XrdOucGatherConf.cc +++ b/src/XrdOuc/XrdOucGatherConf.cc @@ -27,6 +27,8 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ +#include + #include #include #include @@ -40,21 +42,40 @@ #include "XrdOuc/XrdOucStream.hh" #include "XrdOuc/XrdOucString.hh" #include "XrdOuc/XrdOucTList.hh" +#include "XrdOuc/XrdOucTokenizer.hh" #include "XrdSys/XrdSysError.hh" +/******************************************************************************/ +/* L o c a l O b j e c t s */ +/******************************************************************************/ + +struct XrdOucGatherConfData +{ +XrdOucTokenizer Tokenizer; +XrdSysError *eDest = 0; +XrdOucString lline; +XrdOucTList *Match = 0; +char *gBuff = 0; +bool echobfr = false; + + XrdOucGatherConfData(XrdSysError *eP) + : Tokenizer(0), eDest(eP) {} + ~XrdOucGatherConfData() {} +}; + /******************************************************************************/ /* C o n s t r u c t o r # 1 */ /******************************************************************************/ XrdOucGatherConf::XrdOucGatherConf(const char *want, XrdSysError *errP) - : XrdOucTokenizer(0), eDest(errP), Match(0), gBuff(0) + : gcP(new XrdOucGatherConfData(errP)) { XrdOucString wlist(want), wtoken; int wlen, wPos = 0; while((wPos = wlist.tokenize(wtoken, wPos, ' ')) != -1) {wlen = (wtoken.endswith('.') ? wtoken.length() : 0); - Match = new XrdOucTList(wtoken.c_str(), wlen, Match); + gcP->Match = new XrdOucTList(wtoken.c_str(), wlen, gcP->Match); } } @@ -63,14 +84,14 @@ XrdOucGatherConf::XrdOucGatherConf(const char *want, XrdSysError *errP) /******************************************************************************/ XrdOucGatherConf::XrdOucGatherConf(const char **&want, XrdSysError *errP) - : XrdOucTokenizer(0), eDest(errP), Match(0), gBuff(0) + : gcP(new XrdOucGatherConfData(errP)) { int n, i = 0; while(want[i]) {if ((n = strlen(want[i]))) {if (*(want[i]+(n-1)) != '.') n = 0; - Match = new XrdOucTList(want[i], n, Match); + gcP->Match = new XrdOucTList(want[i], n, gcP->Match); } } } @@ -83,14 +104,40 @@ XrdOucGatherConf::~XrdOucGatherConf() { XrdOucTList *tP; - while((tP = Match)) - {Match = tP->next; + while((tP = gcP->Match)) + {gcP->Match = tP->next; delete tP; } - if (gBuff) free(gBuff); + if (gcP->gBuff) free(gcP->gBuff); +} + +/******************************************************************************/ +/* E c h o L i n e */ +/******************************************************************************/ + +void XrdOucGatherConf::EchoLine() +{ + +// Make sure we can actually display anything +// + if (!(gcP->eDest)) + throw std::invalid_argument("XrdSysError object not supplied!"); + +// Echo only when we have something to echo +// + if (gcP->lline.length()) gcP->eDest->Say("=====> ", gcP->lline.c_str()); } + +/******************************************************************************/ +/* E c h o O r d e r */ +/******************************************************************************/ +void XrdOucGatherConf::EchoOrder(bool doBefore) +{ + gcP->echobfr = doBefore; +} + /******************************************************************************/ /* G a t h e r */ /******************************************************************************/ @@ -98,7 +145,7 @@ XrdOucGatherConf::~XrdOucGatherConf() int XrdOucGatherConf::Gather(const char *cfname, Level lvl, const char *parms) { XrdOucEnv myEnv; - XrdOucStream Config(eDest, getenv("XRDINSTANCE"), &myEnv, "=====> "); + XrdOucStream Config(gcP->eDest, getenv("XRDINSTANCE"), &myEnv, "=====> "); XrdOucTList *tP; XrdOucString theGrab; char *var, drctv[64], body[4096]; @@ -107,17 +154,21 @@ int XrdOucGatherConf::Gather(const char *cfname, Level lvl, const char *parms) // Make sure we have something to compare // - if (!Match) return 0; + if (!(gcP->Match)) return 0; // Reset the buffer if it has been set // - if (gBuff) {free(gBuff); gBuff = 0; Attach(0);} + if (gcP->gBuff) + {free(gcP->gBuff); + gcP->gBuff = 0; + gcP->Tokenizer.Attach(0); + } // Open the config file // if ( (cfgFD = open(cfname, O_RDONLY, 0)) < 0) {rc = errno; - if (eDest) eDest->Emsg("Gcf", rc, "open config file", cfname); + if (gcP->eDest) gcP->eDest->Emsg("Gcf", rc, "open config file", cfname); return -rc; } @@ -141,7 +192,7 @@ int XrdOucGatherConf::Gather(const char *cfname, Level lvl, const char *parms) // Process the config file // while((var = Config.GetMyFirstWord())) - {tP = Match; + {tP = gcP->Match; while(tP && ((tP->val && strncmp(var, tP->text, tP->val)) || (!tP->val && strcmp( var, tP->text)))) tP = tP->next; @@ -153,13 +204,13 @@ int XrdOucGatherConf::Gather(const char *cfname, Level lvl, const char *parms) } int n = snprintf(drctv+1, sizeof(drctv)-1, "%s ", var); if (n >= (int)sizeof(drctv)-1) - {if (eDest) eDest->Emsg("Gcf", E2BIG, "handle", var); + {if (gcP->eDest) gcP->eDest->Emsg("Gcf", E2BIG, "handle", var); return -E2BIG; } } else drctv[1] = 0; if (!Config.GetRest(body, sizeof(body))) - {if (eDest) eDest->Emsg("Gcf", E2BIG, "handle arguments"); + {if (gcP->eDest) gcP->eDest->Emsg("Gcf", E2BIG, "handle arguments"); return -E2BIG; } @@ -173,7 +224,7 @@ int XrdOucGatherConf::Gather(const char *cfname, Level lvl, const char *parms) // Now check if any errors occurred during file i/o // if ((rc = Config.LastError())) - {if (eDest) eDest->Emsg("Gcf", rc, "read config file", cfname); + {if (gcP->eDest) gcP->eDest->Emsg("Gcf", rc, "read config file", cfname); return (rc < 0 ? rc : -rc); } @@ -181,23 +232,192 @@ int XrdOucGatherConf::Gather(const char *cfname, Level lvl, const char *parms) // Copy the grab to a modifiable buffer. // if ((n = theGrab.length()) <= 1) n = 0; - else {gBuff = (char *)malloc(n); - strcpy(gBuff, theGrab.c_str()+1); // skip 1st byte but add null - Attach(gBuff); + else {gcP->gBuff = (char *)malloc(n); + strcpy(gcP->gBuff, theGrab.c_str()+1); // skip 1st byte but add null + gcP->Tokenizer.Attach(gcP->gBuff); n--; } return n; } +/******************************************************************************/ +/* G e t L i n e */ +/******************************************************************************/ + +char* XrdOucGatherConf::GetLine() +{ + char* theLine = gcP->Tokenizer.GetLine(); + + while(theLine && *theLine == 0) theLine = gcP->Tokenizer.GetLine(); + + if (!theLine) gcP->lline = ""; + else gcP->lline = theLine; + + return theLine; +} + +/******************************************************************************/ +/* G e t T o k e n */ +/******************************************************************************/ + +char* XrdOucGatherConf::GetToken(char **rest, int lowcase) +{ + return gcP->Tokenizer.GetToken(rest, lowcase); +} + /******************************************************************************/ /* h a s D a t a */ /******************************************************************************/ bool XrdOucGatherConf::hasData() { - return gBuff != 0 && *gBuff != 0; + return gcP->gBuff != 0 && *(gcP->gBuff) != 0; +} + +/******************************************************************************/ +/* M s g E */ +/******************************************************************************/ + +void XrdOucGatherConf::MsgE(const char* txt1,const char* txt2,const char* txt3, + const char* txt4,const char* txt5,const char* txt6) +{ + const char* mVec[7]; + int n = 0; + + mVec[n++] = "Config mistake:"; + if (txt1) mVec[n++] = txt1; + if (txt2) mVec[n++] = txt2; + if (txt3) mVec[n++] = txt3; + if (txt4) mVec[n++] = txt4; + if (txt5) mVec[n++] = txt5; + if (txt6) mVec[n++] = txt6; + + MsgX(mVec, n+1); +} + +/******************************************************************************/ +/* M s g W */ +/******************************************************************************/ + +void XrdOucGatherConf::MsgW(const char* txt1,const char* txt2,const char* txt3, + const char* txt4,const char* txt5,const char* txt6) +{ + const char* mVec[7]; + int n = 0; + + mVec[n++] = "Config warning:"; + if (txt1) mVec[n++] = txt1; + if (txt2) mVec[n++] = txt2; + if (txt3) mVec[n++] = txt3; + if (txt4) mVec[n++] = txt4; + if (txt5) mVec[n++] = txt5; + if (txt6) mVec[n++] = txt6; + + MsgX(mVec, n+1); } +/******************************************************************************/ +/* M s g X */ +/******************************************************************************/ + +void XrdOucGatherConf::MsgX(const char** mVec, int n) +{ + XrdOucString theMsg(2048); + +// Make sure we can actually display anything +// + if (!(gcP->eDest)) + throw std::invalid_argument("XrdSysError object not supplied!"); + +// Construct the message in a string +// + for (int i = 0; i < n; i++) + {theMsg += mVec[i]; + if (i+1 < n) theMsg += ' '; + } + +// Dislay the last line and the message in the proper order +// + if (gcP->echobfr) EchoLine(); + gcP->eDest->Say(theMsg.c_str()); + if (!(gcP->echobfr)) EchoLine(); +} + +/******************************************************************************/ +/* M s g f E */ +/******************************************************************************/ + +void XrdOucGatherConf::MsgfE(const char *fmt, ...) +{ + char buffer[2048]; + va_list args; + va_start (args, fmt); + +// Format the message +// + vsnprintf(buffer, sizeof(buffer), fmt, args); + +// Go print the message +// + MsgfX("Config mistake: ", buffer); +} + +/******************************************************************************/ +/* M s g f W */ +/******************************************************************************/ + +void XrdOucGatherConf::MsgfW(const char *fmt, ...) +{ + char buffer[2048]; + va_list args; + va_start (args, fmt); + +// Format the message +// + vsnprintf(buffer, sizeof(buffer), fmt, args); + +// Go print the message +// + MsgfX("Config warning: ", buffer); +} + +/******************************************************************************/ +/* M s g f X */ +/******************************************************************************/ + +void XrdOucGatherConf::MsgfX(const char* txt1, const char* txt2) +{ + +// Make sure we can actually display anything +// + if (!(gcP->eDest)) + throw std::invalid_argument("XrdSysError object not supplied!"); + +// Dislay the last line and the message in the proper order +// + if (gcP->echobfr) EchoLine(); + gcP->eDest->Say(txt1, txt2); + if (!(gcP->echobfr)) EchoLine(); +} + +/******************************************************************************/ +/* R e t T o k e n */ +/******************************************************************************/ + +void XrdOucGatherConf::RetToken() +{ + return gcP->Tokenizer.RetToken(); +} + +/******************************************************************************/ +/* T a b s */ +/******************************************************************************/ + +void XrdOucGatherConf::Tabs(int x) +{ + gcP->Tokenizer.Tabs(x); +} + /******************************************************************************/ /* u s e D a t a */ /******************************************************************************/ @@ -206,8 +426,8 @@ bool XrdOucGatherConf::useData(const char *data) { if (!data || *data == 0) return false; - if (gBuff) free(gBuff); - gBuff = strdup(data); - Attach(gBuff); + if (gcP->gBuff) free(gcP->gBuff); + gcP->gBuff = strdup(data); + gcP->Tokenizer.Attach(gcP->gBuff); return true; } diff --git a/src/XrdOuc/XrdOucGatherConf.hh b/src/XrdOuc/XrdOucGatherConf.hh index ce5d6ecd1a7..266607fd169 100644 --- a/src/XrdOuc/XrdOucGatherConf.hh +++ b/src/XrdOuc/XrdOucGatherConf.hh @@ -29,21 +29,38 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ +struct XrdOucGatherConfData; +class XrdSysError; -#include "XrdOuc/XrdOucTokenizer.hh" - -class XrdOucTList; -class XrdSysError; - -class XrdOucGatherConf : public XrdOucTokenizer +class XrdOucGatherConf { public: +//------------------------------------------------------------------------------ +//! Echo the last line retrieved using GetLine() using proper framing. +//! +//! @notes 1) An exception is thrown if a XrdSysError object was not supplied. +//------------------------------------------------------------------------------ + +void EchoLine(); + +//------------------------------------------------------------------------------ +//! Specift the order in which the last line is displayed vs a message. +//! +//! @paramn doBefore - When true, the line is displayed before the message. +//! When false, it is displayed after the message (default). +//! +//! @notes 1) This call is only relevant to calls to MsgE(), MsgW(), MsgfE(), +//! and MsgfW. +//------------------------------------------------------------------------------ + +void EchoOrder(bool doBefore); + //------------------------------------------------------------------------------ //! Gather information from a config file. //! //! @note You must call this method or a successful useData() before calling -//! any XrdOucTokenizer methods. +//! any Get/Ret methods. //! //! @param cfname Path to the configuration file. //! @param lvl Indicates how the gathered directives are to be saved: @@ -72,6 +89,31 @@ enum Level {full_lines = 0, //!< Complete lines int Gather(const char *cfname, Level lvl, const char *parms=0); +//------------------------------------------------------------------------------ +//! Sequence to the next line in the configuration file. +//! +//! @return Pointer to the next line that will be tokenized or NIL if there +//! are no more lines left. +//! +//! @notes 1) You must call GetLine() before calling GetToken(). +//------------------------------------------------------------------------------ + +char* GetLine(); + +//------------------------------------------------------------------------------ +//! Get the next blank-delimited token in the record returned by Getline(). +//! +//! @param rest - Address of a char pointer. When specified, a pointer to +//! the first non-blank character after the returned token. +//! @param lowcasee - When 1, all characters are converted to lower case. +//! When 0, the default, the characters are not changed. +//! +//! @return A pointer to the next token. If the end of the line has been +//! reached, a NIL pointer is returned. +//------------------------------------------------------------------------------ + +char* GetToken(char **rest=0, int lowcase=0); + //------------------------------------------------------------------------------ //! Check if data is present. //! @@ -80,10 +122,71 @@ int Gather(const char *cfname, Level lvl, const char *parms=0); bool hasData(); +//----------------------------------------------------------------------------- +//! Display a space delimited error/warning message. +//! +//! @param txt1,txt2,txt3,txt4,txt5,txt6 the message components formatted as +//! "txt1 [txt2] [txt3] [txt4] [txt5] [txt6]" +//! +//! @notes 1) This methods throws an exception if a XrdSysError object was not +//! passed to the constructor. +//! 2) The last line returned by this object will be displayed either +//! before or after the message (see EchoOrder()). +//! 3) Messages are truncated at 2048 bytes. +//! 4} Use MsgE for errors. The text is prefixed by "Config mistake:" +//! Use MsgW for warnings. The text is prefixed by "Config warning:" +//----------------------------------------------------------------------------- + +void MsgE(const char* txt1, const char* txt2=0, const char* txt3=0, + const char* txt4=0, const char* txt5=0, const char* txt6=0); + +void MsgW(const char* txt1, const char* txt2=0, const char* txt3=0, + const char* txt4=0, const char* txt5=0, const char* txt6=0); + +//----------------------------------------------------------------------------- +//! Display a formated error/warning message using variable args (i.e. vprintf). +//! +//! @param fmt the message formatting template (i.e. printf format). +//! @param ... the arguments that should be used with the template. The +//! formatted message is truncated at 2048 bytes. +//! +//! @notes 1) This methods throws an exception if a XrdSysError object was not +//! passed to the constructor. +//! 2) The last line returned by this object will be displayed either +//! before or after the message (see EchoOrder()). +//! 3) Messages are truncated at 2048 bytes. +//! 4} Use MsgfE for errors. The text is prefixed by "Config mistake:" +//! Use MsgfW for warnings. The text is prefixed by "Config warning:" +//----------------------------------------------------------------------------- + +void MsgfE(const char *fmt, ...); + +void MsgfW(const char *fmt, ...); + +//------------------------------------------------------------------------------ +//! Backups the token scanner just prior to the last returned token. +//! +//! @notes 1) Only one backup is allowed. Calling RetToken() more than once +//! without an intervening GetToken() call results in undefined +//! behaviour. +//! 2) This call is useful for backing up due to an overscan. +//------------------------------------------------------------------------------ + +void RetToken(); + +//------------------------------------------------------------------------------ +//! Specify how tag characters should be handled. +//! +//! @param x - When 0, tabs are converted to spaces. +//! When 1, tabs are untouched (the default). +//------------------------------------------------------------------------------ + +void Tabs(int x=1); + //------------------------------------------------------------------------------ //! Attempt to use pre-existing data. //! -//! @param data Pointer to null terminated pre-existing data. +//! @param data - Pointer to null terminated pre-existing data. //! //! @return False if the pointer is nil or points to a null string; true o/w. //------------------------------------------------------------------------------ @@ -94,8 +197,8 @@ bool useData(const char *data); //! Constructor #1 //! //! @note This object collects relevant configuration directives ready to be -//! processed by the inherited XrdOucTokenizer methods. All if-fi, set, -//! and variable substitutions are performed. +//! processed by the Get/Ret methods. All if-fi, set, and variable +//! substitutions are performed. //! //! @param want A space separated list of directive prefixes (i.e. end with a //! dot) and actual directives that should be gathered. @@ -125,8 +228,9 @@ bool useData(const char *data); private: -XrdSysError *eDest; -XrdOucTList *Match; -char *gBuff; +void MsgX(const char** mVec, int n); +void MsgfX(const char* txt1, const char* txt2); + +XrdOucGatherConfData *gcP; }; #endif From 0eb71c48a6985865e6e958a857b07bbfd6200bcb Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 19 Mar 2024 17:09:15 -0700 Subject: [PATCH 687/773] sc] Make XrdOucGatherConf.hh a public header. --- src/XrdHeaders.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdHeaders.cmake b/src/XrdHeaders.cmake index e07de6f1542..c723a6a7a81 100644 --- a/src/XrdHeaders.cmake +++ b/src/XrdHeaders.cmake @@ -33,6 +33,7 @@ set( XROOTD_PUBLIC_HEADERS XrdOuc/XrdOucDLlist.hh XrdOuc/XrdOucEnv.hh XrdOuc/XrdOucErrInfo.hh + XrdOuc/XrdOucGatherConf.hh XrdOuc/XrdOucGMap.hh XrdOuc/XrdOucHash.hh XrdOuc/XrdOucHash.icc From 881ae0bb5fac022b23950620dff483c42549b837 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 19 Mar 2024 17:59:11 -0700 Subject: [PATCH 688/773] [Ouc] Proide method to get the last line from XrdOucGatherConf. --- src/XrdOuc/XrdOucGatherConf.cc | 10 ++++++++++ src/XrdOuc/XrdOucGatherConf.hh | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/XrdOuc/XrdOucGatherConf.cc b/src/XrdOuc/XrdOucGatherConf.cc index 0d0235d9f99..cf70d4963c0 100644 --- a/src/XrdOuc/XrdOucGatherConf.cc +++ b/src/XrdOuc/XrdOucGatherConf.cc @@ -274,6 +274,16 @@ bool XrdOucGatherConf::hasData() return gcP->gBuff != 0 && *(gcP->gBuff) != 0; } +/******************************************************************************/ +/* L a s t L i n e */ +/******************************************************************************/ + +const char* XrdOucGatherConf::LastLine() +{ + if (gcP->lline.capacity() == 0) return ""; + return gcP->lline.c_str(); +} + /******************************************************************************/ /* M s g E */ /******************************************************************************/ diff --git a/src/XrdOuc/XrdOucGatherConf.hh b/src/XrdOuc/XrdOucGatherConf.hh index 266607fd169..135c5559b38 100644 --- a/src/XrdOuc/XrdOucGatherConf.hh +++ b/src/XrdOuc/XrdOucGatherConf.hh @@ -114,6 +114,15 @@ char* GetLine(); char* GetToken(char **rest=0, int lowcase=0); +//------------------------------------------------------------------------------ +//! Get the last line. +//! +//! @return pointer to the last line. If no last line exists a null string is +//! returned. The pointer is valid until GetLine() is called. +//------------------------------------------------------------------------------ + +const char* LastLine(); + //------------------------------------------------------------------------------ //! Check if data is present. //! From b5d52aa5574b4074d144defb37809bf0a2f84f18 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 19 Mar 2024 21:53:22 -0700 Subject: [PATCH 689/773] [Posix] Correct xml cache summary report. Fixes #2219 --- 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 e08c651971c..e43e4125f85 100644 --- a/src/XrdPosix/XrdPosixConfig.cc +++ b/src/XrdPosix/XrdPosixConfig.cc @@ -563,7 +563,7 @@ int XrdPosixConfig::Stats(const char *theID, char *buff, int blen) static const char stats2[] = "" "%lld%lld%lld" "%lld%lld" - "%lld>%lld" + "%lld%lld" "" "%lld%lld" "%lld%lld" From ddff1744438a0872db77b9d6ef2fa8ecd50d3cbc Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 17 Mar 2024 16:42:27 +0100 Subject: [PATCH 690/773] [CMake] Check result of configuration step and stop if it fails --- test.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test.cmake b/test.cmake index f86d52b10dc..e0f75a4238f 100644 --- a/test.cmake +++ b/test.cmake @@ -200,10 +200,17 @@ if(EXISTS "${CTEST_GIT_COMMAND}") endif() section("Configure") -ctest_configure(OPTIONS "${CMAKE_ARGS}") +ctest_configure(OPTIONS "${CMAKE_ARGS}" RETURN_VALUE CONFIG_RESULT) ctest_read_custom_files("${CTEST_BINARY_DIRECTORY}") list(APPEND CTEST_NOTES_FILES ${CTEST_BINARY_DIRECTORY}/CMakeCache.txt) + +if(NOT ${CONFIG_RESULT} EQUAL 0) + if(CDASH OR (DEFINED ENV{CDASH} AND "$ENV{CDASH}")) + ctest_submit() + endif() + message(FATAL_ERROR "Configuration failed") +endif() endsection() section("Build") From aed4d7f1b89064283aeaadf0ea31acc1c6880239 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 17 Mar 2024 16:40:22 +0100 Subject: [PATCH 691/773] [CI] Use mk-build-deps to install dependencies on Ubuntu --- .github/workflows/CI.yml | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5599a5de7c1..ee8da1fa9f9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -240,42 +240,19 @@ jobs: CMAKE_ARGS: '-DINSTALL_PYTHON_BINDINGS=0;-DUSE_SYSTEM_ISAL=1;-DCMAKE_INSTALL_PREFIX=/usr' steps: - - name: Install dependencies + - name: Install development tools run: | - sudo apt update -q - sudo apt install -y \ - cmake \ - clang \ - davix-dev \ - g++ \ - 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 + 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 and Test with CTest run: env CC=${CC} CXX=${CC/g*/g++} ctest -VV -S test.cmake From 387f9b917f7e3292bd895c1d9b1f826ba497ad50 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 19 Mar 2024 13:36:57 +0100 Subject: [PATCH 692/773] [CMake] Add space after pull request user info in CDash build name --- test.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.cmake b/test.cmake index e0f75a4238f..87474fa02db 100644 --- a/test.cmake +++ b/test.cmake @@ -90,7 +90,7 @@ if(DEFINED ENV{GITHUB_ACTIONS}) 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})") + 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})") From 0cfa4081feb44b34095ee36512e88fceabc7617d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 20 Mar 2024 08:57:33 +0100 Subject: [PATCH 693/773] [XrdClTls] Prevent concurrent calls to InitTLS() The function InitTLS() can be called either from the constructor of XrdCl::Tls(), or from XRootDTransport::InitProtocolReq(), while the protocol initialization is happening. Since these calls can happen concurrently, for example, when called as a socket callback during the initial connection setup, there can be a crash if two threads try to assign to the unique_ptr at the same time. Fixes: #2220 --- src/XrdCl/XrdClTls.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/XrdCl/XrdClTls.cc b/src/XrdCl/XrdClTls.cc index a788b7c2229..7f178f94063 100644 --- a/src/XrdCl/XrdClTls.cc +++ b/src/XrdCl/XrdClTls.cc @@ -27,6 +27,7 @@ #include "XrdTls/XrdTlsContext.hh" #include "XrdOuc/XrdOucUtils.hh" +#include #include #include @@ -94,6 +95,9 @@ namespace XrdCl { bool InitTLS() { + static std::mutex tls_mutex; + std::lock_guard tls_lock(tls_mutex); + if (tlsContext) return true; From 5794cbcdc3ceebcf874eccee72770593cae23b6d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 20 Mar 2024 13:25:34 +0100 Subject: [PATCH 694/773] [XrdCeph] Fix clang warning about unused expression result Caused by .first in the second statement. Since we are touching these lines, use map.emplace() to simplify the code as well. --- src/XrdCeph/XrdCephPosix.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index d9c085e2500..3eb32a28ef2 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -573,9 +573,8 @@ int checkAndCreateStriper(unsigned int cephPoolIdx, std::string &userAtPool, con return 0; } IOCtxDict & ioDict = g_ioCtx[cephPoolIdx]; - ioDict.insert(std::pair(userAtPool, ioctx)); - sDict.insert(std::pair - (userAtPool, striper)).first; + ioDict.emplace(userAtPool, ioctx); + sDict.emplace(userAtPool, striper); } return 1; } From 90968349e8c980fce4d52adcef3efbfe67c7429d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Feb 2024 22:10:55 +0100 Subject: [PATCH 695/773] [CI] Move GitHub Actions builds to actions/checkout@v4 Required due to GitHub Actions migration to node 20: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20 Note: CentOS 7 checkout fails with v4, so must use v1 (will be removed at EOL later). --- .github/workflows/CI.yml | 14 ++++++-------- .github/workflows/DEB.yml | 4 ++-- .github/workflows/QEMU.yml | 2 +- .github/workflows/RPM.yml | 6 +++--- .github/workflows/python.yml | 2 +- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ee8da1fa9f9..434369197e9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -71,9 +71,7 @@ jobs: zlib-dev - name: Clone repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 + uses: actions/checkout@v1 - name: Setup GitHub runner user within container run: adduser -D --uid 1001 runner && chown -R runner:runner ${GITHUB_WORKSPACE} @@ -134,7 +132,7 @@ jobs: dnf config-manager --set-enabled powertools - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -170,7 +168,7 @@ jobs: dnf config-manager --set-enabled crb - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -205,7 +203,7 @@ jobs: run: dnf install -y dnf-plugins-core git ninja-build rpmdevtools - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -246,7 +244,7 @@ jobs: sudo apt install -y build-essential devscripts equivs git - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -291,7 +289,7 @@ jobs: run: python3 -m pip install --upgrade pip setuptools wheel - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/DEB.yml b/.github/workflows/DEB.yml index 4f6c7a34f41..a29810084a0 100644 --- a/.github/workflows/DEB.yml +++ b/.github/workflows/DEB.yml @@ -43,7 +43,7 @@ jobs: apt install -y build-essential devscripts git - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -87,7 +87,7 @@ jobs: sudo apt install -y build-essential devscripts equivs git - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/QEMU.yml b/.github/workflows/QEMU.yml index ae9534570da..bedd6c63bbf 100644 --- a/.github/workflows/QEMU.yml +++ b/.github/workflows/QEMU.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/RPM.yml b/.github/workflows/RPM.yml index 423a1a4b939..7343780b876 100644 --- a/.github/workflows/RPM.yml +++ b/.github/workflows/RPM.yml @@ -72,7 +72,7 @@ jobs: run: yum install -y git - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -117,7 +117,7 @@ jobs: run: yum install -y git - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -162,7 +162,7 @@ jobs: run: yum install -y git - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index b27d40c20ba..e2bebb71b44 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -39,7 +39,7 @@ jobs: run: dnf install -y git krb5-devel libuuid-devel openssl-devel - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 From bb455752c68143bc7a3d8e79d3cf63c5db9c299a Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 11 Nov 2023 12:14:51 +0100 Subject: [PATCH 696/773] Explicitly control the authorization strategy for tokens In some cases, a token may be mapped to a known user - but we only want to utilize the mapping if there's scope-based authorization in place. This can now be controlled as part of the scitokens.cfg. In the issuer section, one can add: ``` authorization_strategy = strategy1 strategy2 strategy3 ``` Where possible strategies are `capability` (if authorized based on scope, pass through the username mapping), `group` (if authorized based on scope, pass through the group information), and `mapping`, (always pass through the explicit mapping, even if there's no scope present). --- src/XrdSciTokens/README.md | 11 +++++ src/XrdSciTokens/XrdSciTokensAccess.cc | 68 +++++++++++++++++++++----- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/XrdSciTokens/README.md b/src/XrdSciTokens/README.md index e26199b2c91..b7c69fdbb04 100644 --- a/src/XrdSciTokens/README.md +++ b/src/XrdSciTokens/README.md @@ -117,6 +117,17 @@ are: 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. + - `authorization_strategy` (optional): One or more authorizations to use from the token. Multiple (space separated) + items may be specified from the following valid values: + + - `capability`: Authorize based on capabilities (e.g., `storage.read:/foo`) from the token. + - `group`: Pass through the request if there's any group present in the token. + - `mapping`: Pass through the request if the user mapping was successful. + + For the `group` and `mapping` cases, the username and group are set in the internal XRootD request credential, + but the final authorization must be done by a subsequent plugin. The default value is `capability group mapping`. + *Note*: if `mapping` is present, then a token without a capability may still have authorized actions. + Group- and Scope-based authorization ------------------------------------ diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 30a1527b3a3..2616c72fff0 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -42,6 +42,13 @@ enum LogMask { All = 0xff }; +enum IssuerAuthz { + Capability = 0x01, + Group = 0x02, + Mapping = 0x04, + Default = 0x07 +}; + std::string LogMaskToString(int mask) { if (mask == LogMask::All) {return "all";} @@ -272,10 +279,12 @@ struct IssuerConfig const std::vector &base_paths, const std::vector &restricted_paths, bool map_subject, + uint32_t authz_strategy, const std::string &default_user, const std::string &username_claim, const std::vector rules) : m_map_subject(map_subject || !username_claim.empty()), + m_authz_strategy(authz_strategy), m_name(issuer_name), m_url(issuer_url), m_default_user(default_user), @@ -286,6 +295,7 @@ struct IssuerConfig {} const bool m_map_subject; + const uint32_t m_authz_strategy; const std::string m_name; const std::string m_url; const std::string m_default_user; @@ -337,7 +347,9 @@ class XrdAccRules { public: XrdAccRules(uint64_t expiry_time, const std::string &username, const std::string &token_subject, - const std::string &issuer, const std::vector &rules, const std::vector &groups) : + const std::string &issuer, const std::vector &rules, const std::vector &groups, + uint32_t authz_strategy) : + m_authz_strategy(authz_strategy), m_expiry_time(expiry_time), m_username(username), m_token_subject(token_subject), @@ -420,10 +432,13 @@ class XrdAccRules const std::string & get_default_username() const {return m_username;} const std::string & get_issuer() const {return m_issuer;} + uint32_t get_authz_strategy() const {return m_authz_strategy;} + size_t size() const {return m_rules.size();} const std::vector &groups() const {return m_groups;} private: + uint32_t m_authz_strategy; AccessRulesRaw m_rules; uint64_t m_expiry_time{0}; const std::string m_username; @@ -513,8 +528,9 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, std::string issuer; std::vector map_rules; std::vector groups; - if (GenerateAcls(authz, cache_expiry, rules, username, token_subject, issuer, map_rules, groups)) { - access_rules.reset(new XrdAccRules(now + cache_expiry, username, token_subject, issuer, map_rules, groups)); + uint32_t authz_strategy; + if (GenerateAcls(authz, cache_expiry, rules, username, token_subject, issuer, map_rules, groups, authz_strategy)) { + access_rules.reset(new XrdAccRules(now + cache_expiry, username, token_subject, issuer, map_rules, groups, authz_strategy)); access_rules->parse(rules); } else { m_log.Log(LogMask::Warning, "Access", "Failed to generate ACLs for token"); @@ -533,7 +549,8 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, m_log.Log(LogMask::Debug, "Access", "Cached token", access_rules->str().c_str()); } - // Strategy: we populate the name in the XrdSecEntity if: + // Strategy: assuming the corresponding strategy is enabled, we populate the name in + // the XrdSecEntity if: // 1. There are scopes present in the token that authorize the request, // 2. The token is mapped by some rule in the mapfile (group or subject-based mapping). // The default username for the issuer is only used in (1). @@ -553,7 +570,8 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, if (!issuer.empty()) { new_secentity.vorg = strdup(issuer.c_str()); } - if (access_rules->groups().size()) { + bool group_success = false; + if ((access_rules->get_authz_strategy() & IssuerAuthz::Group) && access_rules->groups().size()) { std::stringstream ss; for (const auto &grp : access_rules->groups()) { ss << grp << " "; @@ -564,6 +582,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, memcpy(new_secentity.grps, groups_str.c_str(), groups_str.size()); new_secentity.grps[groups_str.size()] = '\0'; } + group_success = true; } std::string username; @@ -571,15 +590,15 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, bool scope_success = false; username = access_rules->get_username(path); - mapping_success = !username.empty(); - scope_success = access_rules->apply(oper, path); + mapping_success = (access_rules->get_authz_strategy() & IssuerAuthz::Mapping) && !username.empty(); + scope_success = (access_rules->get_authz_strategy() & IssuerAuthz::Capability) && access_rules->apply(oper, path); if (scope_success && (m_log.getMsgMask() & LogMask::Debug)) { std::stringstream ss; ss << "Grant authorization based on scopes for operation=" << OpToName(oper) << ", path=" << path; m_log.Log(LogMask::Debug, "Access", ss.str().c_str()); } - if (!scope_success && !mapping_success) { + if (!scope_success && !mapping_success && !group_success) { auto returned_accs = OnMissing(&new_secentity, path, oper, env); // Clean up the new_secentity if (new_secentity.vorg != nullptr) free(new_secentity.vorg); @@ -590,11 +609,13 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, } // Default user only applies to scope-based mappings. - if (!mapping_success && scope_success) { - mapping_success = !(username = access_rules->get_default_username()).empty(); + if (scope_success && username.empty()) { + username = access_rules->get_default_username(); } - if (mapping_success) { + // Setting the request.name will pass the username to the next plugin. + // Ensure we do that only if map-based or scope-based authorization worked. + if (scope_success || mapping_success) { // Set scitokens.name in the extra attribute Entity->eaAPI->Add("request.name", username, true); new_secentity.eaAPI->Add("request.name", username, true); @@ -730,7 +751,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, return XrdAccPriv_None; } - bool GenerateAcls(const std::string &authz, uint64_t &cache_expiry, AccessRulesRaw &rules, std::string &username, std::string &token_subject, std::string &issuer, std::vector &map_rules, std::vector &groups) { + bool GenerateAcls(const std::string &authz, uint64_t &cache_expiry, AccessRulesRaw &rules, std::string &username, std::string &token_subject, std::string &issuer, std::vector &map_rules, std::vector &groups, uint32_t &authz_strategy) { // Does this look like a JWT? If not, bail out early and // do not pollute the log. bool looks_good = true; @@ -962,6 +983,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, xrd_rules.emplace_back(AOP_Delete, write_path); } } + authz_strategy = config.m_authz_strategy; pthread_rwlock_unlock(&m_config_lock); @@ -1250,10 +1272,30 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, auto map_subject = reader.GetBoolean(section, "map_subject", false); auto username_claim = reader.Get(section, "username_claim", ""); + auto authz_strategy_str = reader.Get(section, "authorization_strategy", ""); + uint32_t authz_strategy = 0; + if (authz_strategy_str.empty()) { + authz_strategy = IssuerAuthz::Default; + } else { + std::istringstream authz_strategy_stream(authz_strategy_str); + std::string authz_str; + while (std::getline(authz_strategy_stream, authz_str, ' ')) { + if (!strcasecmp(authz_str.c_str(), "capability")) { + authz_strategy |= IssuerAuthz::Capability; + } else if (!strcasecmp(authz_str.c_str(), "group")) { + authz_strategy |= IssuerAuthz::Group; + } else if (!strcasecmp(authz_str.c_str(), "mapping")) { + authz_strategy |= IssuerAuthz::Mapping; + } else { + m_log.Log(LogMask::Error, "Reconfig", "Unknown authorization strategy (ignoring):", authz_str.c_str()); + } + } + } + issuers.emplace(std::piecewise_construct, std::forward_as_tuple(issuer), std::forward_as_tuple(name, issuer, base_paths, restricted_paths, - map_subject, default_user, username_claim, rules)); + map_subject, authz_strategy, default_user, username_claim, rules)); } if (issuers.empty()) { From 659a8f273911b24993daf927f74037f1fd8d5e48 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 22 Mar 2024 10:23:14 +0100 Subject: [PATCH 697/773] [CMake] Move baseline required C++ standard to C++17 --- cmake/XRootDOSDefs.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/XRootDOSDefs.cmake b/cmake/XRootDOSDefs.cmake index 5f1ebc71546..342838ef737 100644 --- a/cmake/XRootDOSDefs.cmake +++ b/cmake/XRootDOSDefs.cmake @@ -18,7 +18,7 @@ define_default( LIBRARY_PATH_PREFIX "lib" ) #------------------------------------------------------------------------------- # Enable c++14 #------------------------------------------------------------------------------- -set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ Standard") +set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ Standard") set(CMAKE_CXX_STANDARD_REQUIRED TRUE) if( ENABLE_ASAN ) From 6102ea88296f0046057f4a72541394e9a7fe8827 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 25 Mar 2024 14:47:37 +0100 Subject: [PATCH 698/773] [Tests] Update header check to use C++17 standard --- tests/check-headers.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/check-headers.sh b/tests/check-headers.sh index eba2ddd65e6..b9cc1d1dec8 100755 --- a/tests/check-headers.sh +++ b/tests/check-headers.sh @@ -10,7 +10,7 @@ # shellcheck disable=SC2086 : "${CXX:=c++}" -: "${CXXFLAGS:=-Wall}" +: "${CXXFLAGS:=-Wall -std=c++17}" : "${INCLUDE_DIR:=${1:-/usr/include/xrootd}}" if ! command -v "${CXX}" >/dev/null; then From c95e804624fe01ac6d6cc2a46ee3cd02d1055266 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 22 Mar 2024 10:26:57 +0100 Subject: [PATCH 699/773] [XrdEc] Replace std::result_of with std::invoke_result std::result_of is deprecated in C++17 and removed in C++20. --- src/XrdEc/XrdEcThreadPool.hh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/XrdEc/XrdEcThreadPool.hh b/src/XrdEc/XrdEcThreadPool.hh index 0b77b0638cf..df65cc4157c 100644 --- a/src/XrdEc/XrdEcThreadPool.hh +++ b/src/XrdEc/XrdEcThreadPool.hh @@ -23,7 +23,9 @@ //------------------------------------------------------------------------------ #include "XrdCl/XrdClJobManager.hh" + #include +#include #ifndef SRC_XRDEC_XRDECTHREADPOOL_HH_ #define SRC_XRDEC_XRDECTHREADPOOL_HH_ @@ -157,11 +159,11 @@ namespace XrdEc //! Schedule a functional (together with its arguments) for execution //----------------------------------------------------------------------- template - inline std::future::type> + inline std::future> Execute( FUNC func, ARGs... args ) { - using RET = typename std::result_of::type; - AnyJob *job = new AnyJob( func, std::move( args )... ); + using RET = std::invoke_result_t; + auto *job = new AnyJob( func, std::move( args )... ); std::future ftr = job->GetFuture(); threadpool.QueueJob( job, nullptr ); return ftr; From ed7ed26d3c769790d2082c0507b373a2719f30ae Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Feb 2024 22:16:02 +0100 Subject: [PATCH 700/773] [CI] Move GitHub Actions to actions/upload-artifact@v4 Required due to GitHub Actions migration to node 20: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20 --- .github/workflows/DEB.yml | 24 ++++++++++++------------ .github/workflows/RPM.yml | 22 +++++++++++----------- .github/workflows/python.yml | 6 +++--- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/DEB.yml b/.github/workflows/DEB.yml index a29810084a0..782faddf1b6 100644 --- a/.github/workflows/DEB.yml +++ b/.github/workflows/DEB.yml @@ -66,15 +66,15 @@ jobs: - name: Move DEBs to Artifact Directory run: | source /etc/os-release - mkdir -p DEB/${ID}/${VERSION_CODENAME} - mv ../*.* DEB/${ID}/${VERSION_CODENAME} + mkdir -p debian/${VERSION_CODENAME} + mv ../*.* debian/${VERSION_CODENAME} - name: Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: DEB - path: DEB - retention-days: 1 + name: debian-${{ matrix.version }} + path: debian + retention-days: 14 ubuntu: name: Ubuntu (22.04) @@ -110,12 +110,12 @@ jobs: - name: Move DEBs to Artifact Directory run: | source /etc/os-release - mkdir -p DEB/${ID}/${VERSION_CODENAME} - mv ../*.* DEB/${ID}/${VERSION_CODENAME} + mkdir -p ubuntu/${VERSION_CODENAME} + mv ../*.* ubuntu/${VERSION_CODENAME} - name: Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: DEB - path: DEB - retention-days: 1 + name: ubuntu-22.04 + path: ubuntu + retention-days: 14 diff --git a/.github/workflows/RPM.yml b/.github/workflows/RPM.yml index 7343780b876..70b4e7667a5 100644 --- a/.github/workflows/RPM.yml +++ b/.github/workflows/RPM.yml @@ -58,9 +58,9 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v3 with: - name: RPM + name: centos7 path: RPMS - retention-days: 1 + retention-days: 14 alma8: name: Alma Linux 8 @@ -101,11 +101,11 @@ jobs: run: mkdir RPMS && mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) - name: Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: RPM + name: alma8 path: RPMS - retention-days: 1 + retention-days: 14 alma9: name: Alma Linux 9 @@ -146,11 +146,11 @@ jobs: run: mkdir RPMS && mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) - name: Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: RPM + name: alma9 path: RPMS - retention-days: 1 + retention-days: 14 fedora: name: Fedora 39 @@ -190,8 +190,8 @@ jobs: run: mkdir RPMS && mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) - name: Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: RPM + name: fc39 path: RPMS - retention-days: 1 + retention-days: 14 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index e2bebb71b44..80324fab377 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -68,8 +68,8 @@ jobs: run: mv *.whl dist/ - name: Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: Python + name: python-${{ matrix.version }} path: dist - retention-days: 1 + retention-days: 14 From 548fdc2a153514eca099ecd49bac96fdbf74632d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 28 Mar 2024 08:44:29 +0100 Subject: [PATCH 701/773] [XrdSciTokens] Fix application of access rules when base path is '/' The comparison path[rule.second.size()] == '/' for ensuring path is a subdirectory of the rule's path does not work for '/'. For example if the rule's path is '/' and requested path is '/tmp', then the comparison above checks path[rule.second.size()] == path[1] == '/', but actually, path[1] == 't'. --- src/XrdSciTokens/XrdSciTokensAccess.cc | 50 +++++++++++++++----------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 2616c72fff0..0428b5cc0fe 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -361,28 +361,36 @@ class XrdAccRules ~XrdAccRules() {} bool apply(Access_Operation oper, std::string path) { - for (const auto & rule : m_rules) { - // 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; - } - // according to WLCG token specs, allow creation of required superfolders for a new file if requested - 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; - } + auto is_subdirectory = [](const std::string& dir, const std::string& subdir) { + if (subdir.size() < dir.size()) + return false; + + if (subdir.compare(0, dir.size(), dir, 0, dir.size()) != 0) + return false; + + return dir.size() == subdir.size() || subdir[dir.size()] == '/' || dir == "/"; + }; + + for (const auto & rule : m_rules) { + // Skip rules that don't match the current operation + if (rule.first != oper) + continue; + + // If the rule allows any path, allow the operation + if (rule.second == "/") + return true; + + // Allow operation if path is a subdirectory of the rule's path + if (is_subdirectory(rule.second, path)) { + return true; + } else { + // Allow stat and mkdir of parent directories to comply with WLCG token specs + if (oper == AOP_Stat || oper == AOP_Mkdir) + if (is_subdirectory(path, rule.second)) + return true; } - return false; + } + return false; } bool expired() const {return monotonic_time() > m_expiry_time;} From bae878fe89d168ce99b687056b34db7a66e131c2 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Mon, 25 Mar 2024 20:36:01 +0100 Subject: [PATCH 702/773] Minor test fixes --- tests/XrdCl/XrdClFileCopyTest.cc | 4 ++-- tests/XrdCl/XrdClPoller.cc | 7 +++++-- tests/XrdCl/XrdClSocket.cc | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/XrdCl/XrdClFileCopyTest.cc b/tests/XrdCl/XrdClFileCopyTest.cc index d991b358e15..deb452495ac 100644 --- a/tests/XrdCl/XrdClFileCopyTest.cc +++ b/tests/XrdCl/XrdClFileCopyTest.cc @@ -616,8 +616,8 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) // Copy from a non-existent source //---------------------------------------------------------------------------- results.Clear(); - properties.Set( "source", "root://localhost:9999//test" ); - properties.Set( "target", targetURL ); + 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 ) ); diff --git a/tests/XrdCl/XrdClPoller.cc b/tests/XrdCl/XrdClPoller.cc index 5569c64799b..d308741701a 100644 --- a/tests/XrdCl/XrdClPoller.cc +++ b/tests/XrdCl/XrdClPoller.cc @@ -216,7 +216,10 @@ TEST(PollerTest, FunctionTest) //---------------------------------------------------------------------------- Server server( Server::Both ); Socket s[3]; - EXPECT_TRUE( server.Setup( 9999, 3, new RandomPumpHandlerFactory() ) ); + uint16_t port = 9996; // 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( server.Setup( port, 3, new RandomPumpHandlerFactory() ) ); EXPECT_TRUE( server.Start() ); EXPECT_TRUE( poller->Initialize() ); EXPECT_TRUE( poller->Start() ); @@ -228,7 +231,7 @@ TEST(PollerTest, FunctionTest) for( int i = 0; i < 3; ++i ) { GTEST_ASSERT_XRDST( s[i].Initialize() ); - GTEST_ASSERT_XRDST( s[i].Connect( "localhost", 9999 ) ); + GTEST_ASSERT_XRDST( s[i].Connect( "localhost", port ) ); EXPECT_TRUE( poller->AddSocket( &s[i], handler ) ); EXPECT_TRUE( poller->EnableReadNotification( &s[i], true, 60 ) ); EXPECT_TRUE( poller->IsRegistered( &s[i] ) ); diff --git a/tests/XrdCl/XrdClSocket.cc b/tests/XrdCl/XrdClSocket.cc index df8f433bff5..c2e42a424f0 100644 --- a/tests/XrdCl/XrdClSocket.cc +++ b/tests/XrdCl/XrdClSocket.cc @@ -223,8 +223,8 @@ TEST(SocketTest, TransferTest) // 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 + // 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() ); From 15ae0dc89152ec492f971687c07b8ecea1eceba1 Mon Sep 17 00:00:00 2001 From: Simon Thiele Date: Mon, 22 Jan 2024 16:05:30 +0100 Subject: [PATCH 703/773] [XrdSciTokens] Implement ability to have token groups as a separate claim --- src/XrdSciTokens/XrdSciTokensAccess.cc | 36 ++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 0428b5cc0fe..b7e69c1fc19 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -282,6 +282,7 @@ struct IssuerConfig uint32_t authz_strategy, const std::string &default_user, const std::string &username_claim, + const std::string &groups_claim, const std::vector rules) : m_map_subject(map_subject || !username_claim.empty()), m_authz_strategy(authz_strategy), @@ -289,6 +290,7 @@ struct IssuerConfig m_url(issuer_url), m_default_user(default_user), m_username_claim(username_claim), + m_groups_claim(groups_claim), m_base_paths(base_paths), m_restricted_paths(restricted_paths), m_map_rules(rules) @@ -300,6 +302,7 @@ struct IssuerConfig const std::string m_url; const std::string m_default_user; const std::string m_username_claim; + const std::string m_groups_claim; const std::vector m_base_paths; const std::vector m_restricted_paths; const std::vector m_map_rules; @@ -842,9 +845,26 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, } enforcer_destroy(enf); + pthread_rwlock_rdlock(&m_config_lock); + auto iter = m_issuers.find(token_issuer); + if (iter == m_issuers.end()) { + pthread_rwlock_unlock(&m_config_lock); + m_log.Log(LogMask::Warning, "GenerateAcls", "Authorized issuer without a config."); + scitoken_destroy(token); + return false; + } + const auto &config = iter->second; + value = nullptr; + char **group_list; std::vector groups_parsed; - if (!scitoken_get_claim_string_list(token, "wlcg.groups", &group_list, &err_msg)) { + const char* tmp_group_claim; + if (!config.m_groups_claim.empty()) { + tmp_group_claim = config.m_groups_claim.c_str(); + } else { + tmp_group_claim = "wlcg.groups"; + } + if (!scitoken_get_claim_string_list(token, tmp_group_claim, &group_list, &err_msg)) { for (int idx=0; group_list[idx]; idx++) { groups_parsed.emplace_back(group_list[idx]); } @@ -855,17 +875,6 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, free(err_msg); } - - pthread_rwlock_rdlock(&m_config_lock); - auto iter = m_issuers.find(token_issuer); - if (iter == m_issuers.end()) { - pthread_rwlock_unlock(&m_config_lock); - m_log.Log(LogMask::Warning, "GenerateAcls", "Authorized issuer without a config."); - scitoken_destroy(token); - return false; - } - const auto &config = iter->second; - value = nullptr; if (scitoken_get_claim_string(token, "sub", &value, &err_msg)) { pthread_rwlock_unlock(&m_config_lock); m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to get token subject:", err_msg); @@ -1279,6 +1288,7 @@ 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", ""); + auto groups_claim = reader.Get(section, "groups_claim", ""); auto authz_strategy_str = reader.Get(section, "authorization_strategy", ""); uint32_t authz_strategy = 0; @@ -1303,7 +1313,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, issuers.emplace(std::piecewise_construct, std::forward_as_tuple(issuer), std::forward_as_tuple(name, issuer, base_paths, restricted_paths, - map_subject, authz_strategy, default_user, username_claim, rules)); + map_subject, authz_strategy, default_user, username_claim, groups_claim, rules)); } if (issuers.empty()) { From 42c260656372824c475a53baf424270cd53a197c Mon Sep 17 00:00:00 2001 From: Simon Thiele Date: Thu, 25 Jan 2024 10:01:18 +0100 Subject: [PATCH 704/773] [XrdSciTokens] Update README.md about newly introduced groups_claim --- src/XrdSciTokens/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/XrdSciTokens/README.md b/src/XrdSciTokens/README.md index b7c69fdbb04..1edecf94486 100644 --- a/src/XrdSciTokens/README.md +++ b/src/XrdSciTokens/README.md @@ -115,7 +115,9 @@ are: - `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 + - `groups_claim` (optional): Not all issuers put the desired groups in the `wlcg.groups` claim. To use an alternate claim + as the groups, set this to the desired claim name. If not set, the default is `wlcg.groups`. + - `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. - `authorization_strategy` (optional): One or more authorizations to use from the token. Multiple (space separated) items may be specified from the following valid values: @@ -168,7 +170,8 @@ The enumerated keys are: 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 be normalized; if presented with `/home//bbockelm/`, then this is treated as if `/home/bbockelm` was given. - - `group`: Case-sensitive match against one of the groups in the token. + - `group`: Case-sensitive match against one of the groups in the token (the claim specifying the groups is configurable, controlled by the + `groups_claim` variable in the issuer config; default is `wlcg.groups`). - `ignore`: If present (regardless of the value), the rule is ignored. - `comment`: Ignored; reserved for adding comments from the administrator. From a153f8dab92ed3bfda0c68283a54bc555faab87d Mon Sep 17 00:00:00 2001 From: Simon Thiele Date: Mon, 11 Mar 2024 15:19:50 +0100 Subject: [PATCH 705/773] [XrdSciTokens] Use a copy of config in GenerateAcls instead of locking --- src/XrdSciTokens/XrdSciTokensAccess.cc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index b7e69c1fc19..7cd16173d9f 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -853,7 +853,8 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, scitoken_destroy(token); return false; } - const auto &config = iter->second; + const auto config = iter->second; + pthread_rwlock_unlock(&m_config_lock); value = nullptr; char **group_list; @@ -876,7 +877,6 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, } if (scitoken_get_claim_string(token, "sub", &value, &err_msg)) { - pthread_rwlock_unlock(&m_config_lock); m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to get token subject:", err_msg); free(err_msg); scitoken_destroy(token); @@ -888,7 +888,6 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, 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); @@ -1002,8 +1001,6 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, } authz_strategy = config.m_authz_strategy; - pthread_rwlock_unlock(&m_config_lock); - cache_expiry = expiry; rules = std::move(xrd_rules); username = std::move(tmp_username); From 027a51e897fc918d62f7265ff20898e14d06f2d3 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 3 Apr 2024 11:57:39 +0200 Subject: [PATCH 706/773] [XrdSciTokens] Fix indentation of local variables in try block --- src/XrdSciTokens/XrdSciTokensAccess.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 7cd16173d9f..c85a22ff6bc 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -532,8 +532,8 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, if (!access_rules) { m_log.Log(LogMask::Debug, "Access", "Token not found in recent cache; parsing."); try { - uint64_t cache_expiry; - AccessRulesRaw rules; + uint64_t cache_expiry; + AccessRulesRaw rules; std::string username; std::string token_subject; std::string issuer; From 9a2e8b99d95b8bdbe64e1cf9aaf29e9feab27a90 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 2 Apr 2024 15:46:52 +0200 Subject: [PATCH 707/773] [XrdSciTokens] Simplify handling of groups claim --- src/XrdSciTokens/XrdSciTokensAccess.cc | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index c85a22ff6bc..13e26b3f44e 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -859,13 +859,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, char **group_list; std::vector groups_parsed; - const char* tmp_group_claim; - if (!config.m_groups_claim.empty()) { - tmp_group_claim = config.m_groups_claim.c_str(); - } else { - tmp_group_claim = "wlcg.groups"; - } - if (!scitoken_get_claim_string_list(token, tmp_group_claim, &group_list, &err_msg)) { + if (scitoken_get_claim_string_list(token, config.m_groups_claim.c_str(), &group_list, &err_msg) == 0) { for (int idx=0; group_list[idx]; idx++) { groups_parsed.emplace_back(group_list[idx]); } @@ -1285,7 +1279,7 @@ 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", ""); - auto groups_claim = reader.Get(section, "groups_claim", ""); + auto groups_claim = reader.Get(section, "groups_claim", "wlcg.groups"); auto authz_strategy_str = reader.Get(section, "authorization_strategy", ""); uint32_t authz_strategy = 0; From b877013012684f1c7536b9c138dbdd80719c07e6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 3 Apr 2024 14:50:55 +0200 Subject: [PATCH 708/773] [XrdSciTokens] Warn if something goes wrong when parsing token groups --- src/XrdSciTokens/XrdSciTokensAccess.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 13e26b3f44e..944149a0d2a 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -865,8 +865,8 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, } scitoken_free_string_list(group_list); } else { - // For now, we silently ignore errors. - // std::cerr << "Failed to get groups: " << err_msg << std::endl; + // Failing to parse groups is not fatal, but we should still warn about what's wrong + m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to get token groups:", err_msg); free(err_msg); } From 4a7cb0f9786c461d2b734ebb6b10071ec3d096c4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 27 Mar 2024 14:52:29 +0100 Subject: [PATCH 709/773] [CMake] Conditionally append private include directory ROOT only needs XRootD's public headers, and private headers come from a separate RPM. This allows building projects against XRootD that don't need private headers by only installing the necessary public header RPMs. Co-authored-by: Stephan Hageboeck --- cmake/XRootDConfig.cmake.in | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/XRootDConfig.cmake.in b/cmake/XRootDConfig.cmake.in index 0a4ce3fe557..fc630f09520 100644 --- a/cmake/XRootDConfig.cmake.in +++ b/cmake/XRootDConfig.cmake.in @@ -53,7 +53,11 @@ set_and_check(XRootD_DATA_DIR "@PACKAGE_CMAKE_INSTALL_DATADIR@") 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") +set(XRootD_INCLUDE_DIRS "${XRootD_INCLUDE_DIR}") +if(IS_DIRECTORY "${XRootD_INCLUDE_DIR}/private") + list(APPEND XRootD_INCLUDE_DIRS "${XRootD_INCLUDE_DIR}/private") +endif() + set(XROOTD_INCLUDE_DIRS "${XRootD_INCLUDE_DIRS}") # backward compatibility ################################################################################ From aab1824a7cc3ce98c34ce1fd02319ff594802181 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 9 Apr 2024 16:34:09 +0200 Subject: [PATCH 710/773] [XrdCl] Simplify implementation of FileStateHandler::IsRecoverable Loop over a list of recoverable errors and return false without looping if recovering is disabled for both read and write operations. --- src/XrdCl/XrdClFileStateHandler.cc | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/XrdCl/XrdClFileStateHandler.cc b/src/XrdCl/XrdClFileStateHandler.cc index d9491c85c3c..371869f47b2 100644 --- a/src/XrdCl/XrdClFileStateHandler.cc +++ b/src/XrdCl/XrdClFileStateHandler.cc @@ -2879,18 +2879,19 @@ namespace XrdCl //---------------------------------------------------------------------------- bool FileStateHandler::IsRecoverable( const XRootDStatus &status ) const { - if( status.code == errSocketError || status.code == errInvalidSession || - status.code == errTlsError || status.code == errSocketTimeout || - status.code == errOperationInterrupted) - { - if( IsReadOnly() && !pDoRecoverRead ) - return false; + const auto recoverable_errors = { + errSocketError, + errSocketTimeout, + errInvalidSession, + errTlsError, + errOperationInterrupted + }; - if( !IsReadOnly() && !pDoRecoverWrite ) - return false; + if (pDoRecoverRead || pDoRecoverWrite) + for (const auto error : recoverable_errors) + if (status.code == error) + return IsReadOnly() ? pDoRecoverRead : pDoRecoverWrite; - return true; - } return false; } From 36e5e9f9e97cc06ba0e0be7eac1785148199d627 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 9 Apr 2024 16:38:14 +0200 Subject: [PATCH 711/773] [XrdCl] Add errInternal to list of recoverable errors Fixes: #2210 --- src/XrdCl/XrdClFileStateHandler.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdCl/XrdClFileStateHandler.cc b/src/XrdCl/XrdClFileStateHandler.cc index 371869f47b2..49873c27008 100644 --- a/src/XrdCl/XrdClFileStateHandler.cc +++ b/src/XrdCl/XrdClFileStateHandler.cc @@ -2883,6 +2883,7 @@ namespace XrdCl errSocketError, errSocketTimeout, errInvalidSession, + errInternal, errTlsError, errOperationInterrupted }; From 6861a86e440585c432f8c3a4a5f464f86a9c1a1a Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 10 Apr 2024 10:50:06 +0200 Subject: [PATCH 712/773] [XrdMacaroons] Support negative directives in macaroons.trace option Fixes: #2224 --- src/XrdMacaroons/XrdMacaroonsConfigure.cc | 83 +++++++++++------------ 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/XrdMacaroons/XrdMacaroonsConfigure.cc b/src/XrdMacaroons/XrdMacaroonsConfigure.cc index 299d005f4b1..66c2dbd1b3e 100644 --- a/src/XrdMacaroons/XrdMacaroonsConfigure.cc +++ b/src/XrdMacaroons/XrdMacaroonsConfigure.cc @@ -97,53 +97,52 @@ bool Handler::Config(const char *config, XrdOucEnv *env, XrdSysError *log, } -bool Handler::xtrace(XrdOucStream &config_obj, XrdSysError *log) +bool Handler::xtrace(XrdOucStream &Config, XrdSysError *log) { - char *val = config_obj.GetWord(); - if (!val || !val[0]) - { - log->Emsg("Config", "macaroons.trace requires at least one directive [all | error | warning | info | debug | none]"); - return false; - } - // If the config option is given, reset the log mask. - log->setMsgMask(0); + static struct traceopts { const char *opname; enum LogMask opval; } tropts[] = { + { "all", LogMask::All }, + { "error", LogMask::Error }, + { "warning", LogMask::Warning }, + { "info", LogMask::Info }, + { "debug", LogMask::Debug } + }; - do { - if (!strcmp(val, "all")) - { - log->setMsgMask(log->getMsgMask() | LogMask::All); - } - else if (!strcmp(val, "error")) - { - log->setMsgMask(log->getMsgMask() | LogMask::Error); - } - else if (!strcmp(val, "warning")) - { - log->setMsgMask(log->getMsgMask() | LogMask::Warning); - } - else if (!strcmp(val, "info")) - { - log->setMsgMask(log->getMsgMask() | LogMask::Info); - } - else if (!strcmp(val, "debug")) - { - log->setMsgMask(log->getMsgMask() | LogMask::Debug); - } - else if (!strcmp(val, "none")) - { - log->setMsgMask(0); - } - else - { - log->Emsg("Config", "macaroons.trace encountered an unknown directive:", val); - return false; + int i, neg, trval = 0, numopts = sizeof(tropts)/sizeof(struct traceopts); + + char *val = Config.GetWord(); + + if (!val || !*val) { + log->Emsg("Config", "macaroons.trace requires at least one directive" + " [ all | error | warning | info | debug | none | off ]"); + return false; + } + + while (val && *val) { + if (strcmp(val, "off") == 0 || strcmp(val, "none") == 0) { + 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; } - val = config_obj.GetWord(); - } while (val); + } + if (neg) --val; + if (i >= numopts) + log->Emsg("Config", "macaroons.trace: ignoring invalid trace option:", val); + } + val = Config.GetWord(); + } - return true; -} + log->setMsgMask(trval); + return true; +} bool Handler::xmaxduration(XrdOucStream &config_obj, XrdSysError *log, ssize_t &max_duration) { From bb2503cce68e2abf184ee2a96820e811a2e4ac60 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 10 Apr 2024 17:36:12 +0200 Subject: [PATCH 713/773] [XrdSecgsi] Fail CA check when prococol.gsi -ca:verify is set Issue: #2150 --- src/XrdSecgsi/XrdSecProtocolgsi.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.cc b/src/XrdSecgsi/XrdSecProtocolgsi.cc index ff067e9775f..699afb8112c 100644 --- a/src/XrdSecgsi/XrdSecProtocolgsi.cc +++ b/src/XrdSecgsi/XrdSecProtocolgsi.cc @@ -4591,8 +4591,9 @@ bool XrdSecProtocolgsi::VerifyCA(int opt, X509Chain *cca, XrdCryptoFactory *CF) } } else { if (CACheck > caNoVerify) { - // Check self-signature - if (!(verified = cca->CheckCA())) + // Check self-signature and fail if needed + bool checkselfsigned = (CACheck > caVerifyss) ? true : false; + if (!(verified = cca->CheckCA(checkselfsigned))) PRINT("CA certificate self-signed: integrity check failed ("<SubjectHash()<<")"); } else { // Set OK in any case From 937ec8d9bbec479d6a5581bc89f3da9fa0eda2f6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 Apr 2024 09:41:42 +0200 Subject: [PATCH 714/773] [Docs] Add XRootD icon and logos to use with doxygen --- docs/images/xrootd-icon-big.png | Bin 0 -> 62320 bytes docs/images/xrootd-icon.png | Bin 0 -> 9615 bytes docs/images/xrootd-logo.png | Bin 0 -> 92337 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/xrootd-icon-big.png create mode 100644 docs/images/xrootd-icon.png create mode 100644 docs/images/xrootd-logo.png diff --git a/docs/images/xrootd-icon-big.png b/docs/images/xrootd-icon-big.png new file mode 100644 index 0000000000000000000000000000000000000000..421dbe6b9e07682e14ed74ea868e740c30b013cb GIT binary patch literal 62320 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 literal 0 HcmV?d00001 From b7a1597a10130c8cf66593a4cf68045ca0d99030 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 Apr 2024 09:42:55 +0200 Subject: [PATCH 715/773] [Docs] Update doxygen configuration --- Doxyfile | 2748 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 2666 insertions(+), 82 deletions(-) diff --git a/Doxyfile b/Doxyfile index ccb72e4b074..a85babb7c04 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,213 +1,2797 @@ -# Doxyfile 1.3.7 +# Doxyfile 1.10.0 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- -PROJECT_NAME = xrootd -PROJECT_NUMBER = + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = XRootD + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = docs/images/xrootd-icon.png + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = docs/images/xrootd-icon.png + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + OUTPUT_DIRECTORY = doxydoc -CREATE_SUBDIRS = NO + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = YES + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + OUTPUT_LANGUAGE = English -USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + FULL_PATH_NAMES = NO -STRIP_FROM_PATH = -STRIP_FROM_INC_PATH = + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = src/ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + MULTILINE_CPP_IS_BRIEF = NO -DETAILS_AT_TOP = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + INHERIT_DOCS = YES -DISTRIBUTE_GROUP_DOC = NO + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + TAB_SIZE = 8 -ALIASES = + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = icc=C++ + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = GITHUB + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = YES + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = YES + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = YES + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 2 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 0 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + EXTRACT_ALL = YES -EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = YES + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + SORT_BY_SCOPE_NAME = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = NO + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = NO + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = NO + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + #--------------------------------------------------------------------------- -# configuration options related to warning and progress messages +# Configuration options related to warning and progress messages #--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + #--------------------------------------------------------------------------- -# configuration options related to the input files +# Configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = src/ -FILE_PATTERNS = *.hh +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = README.md docs src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.md, *.cc, *.icc, *.hh, *.py + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + RECURSIVE = YES -EXCLUDE = + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = src/XrdHttp/README-CKSUM.md \ + src/XrdOuc/XrdOucJson.hh \ + src/XrdSciTokens/vendor \ + src/XrdXml/tinyxml + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXAMPLE_PATH = -EXAMPLE_PATTERNS = + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */test/* */tests/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + EXAMPLE_RECURSIVE = NO -IMAGE_PATH = -INPUT_FILTER = + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = ./README.md + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- -SOURCE_BROWSER = NO -INLINE_SOURCES = NO + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# multi-line macros, enums or list initialized variables directly into the +# documentation. +# The default value is: NO. + +INLINE_SOURCES = YES + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + VERBATIM_HEADERS = YES + #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + ALPHABETICAL_INDEX = NO -COLS_IN_ALPHA_INDEX = 5 -IGNORE_PREFIX = + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + #--------------------------------------------------------------------------- -# configuration options related to the HTML output +# Configuration options related to the HTML output #--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_ALIGN_MEMBERS = YES + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 210 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = YES + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + GENERATE_HTMLHELP = NO -CHM_FILE = -HHC_LOCATION = + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 4 -GENERATE_TREEVIEW = NO -TREEVIEW_WIDTH = 250 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 300 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=14" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=12" + +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. +# The default value is: YES. + CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + COLLABORATION_GRAPH = YES -UML_LOOK = NO + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = YES + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + GRAPHICAL_HIERARCHY = YES -DOT_IMAGE_FORMAT = png -DOT_PATH = -DOTFILE_DIRS = -MAX_DOT_GRAPH_WIDTH = 1024 -MAX_DOT_GRAPH_HEIGHT = 1024 + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 3 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# https://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = svg + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = YES + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 100 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + DOT_CLEANUP = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- -SEARCHENGINE = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = From bfd663d984a33bdf2be04e5a9109c674cfe860ac Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 Apr 2024 09:44:42 +0200 Subject: [PATCH 716/773] [XrdVoms] Fix doxygen warning about non-existent file --- src/XrdVoms/XrdVomsHttp.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdVoms/XrdVomsHttp.cc b/src/XrdVoms/XrdVomsHttp.cc index 4e7131bc363..047613c35b4 100644 --- a/src/XrdVoms/XrdVomsHttp.cc +++ b/src/XrdVoms/XrdVomsHttp.cc @@ -33,7 +33,7 @@ /** @brief This code is based on the basic architecture shown in * - * @file XrdHttpVoms.cc + * @file XrdVomsHttp.cc * @author Fabrizio Furano * @date November 2013 */ From a4349c1cbccef3e592823a4194eef0befd5c3162 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 Apr 2024 09:45:36 +0200 Subject: [PATCH 717/773] [XrdApps] Fix doxygen warning in XrdClRecordPlugin/README.md --- src/XrdApps/XrdClRecordPlugin/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdApps/XrdClRecordPlugin/README.md b/src/XrdApps/XrdClRecordPlugin/README.md index 5e878a5c0a3..6d02e512eae 100644 --- a/src/XrdApps/XrdClRecordPlugin/README.md +++ b/src/XrdApps/XrdClRecordPlugin/README.md @@ -183,7 +183,7 @@ xrdreplay recording.cvs # ============================================= ``` Most of the output fields are self-explaining. Performance Mark puts the original run-time to the achieved run-time into relation. -The indicates if the IO could potentially be run faster than given by the recording (when > 100%). The Synchronicity measures the amount of IO requests within a given file are overlapping between request and response. A value of 100% indicates synchronous IO, a value towards 0 indicates asynchronous IO. This value does not measure parallelism between files. +The `` indicates if the IO could potentially be run faster than given by the recording (when > 100%). The Synchronicity measures the amount of IO requests within a given file are overlapping between request and response. A value of 100% indicates synchronous IO, a value towards 0 indicates asynchronous IO. This value does not measure parallelism between files. In case of IO errors you will see a response error counter != 0 and a shell return code of -5 (251). ```bash From e247e7e44f5368b5f7fbeaf5411ca663acb5dd39 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 Apr 2024 11:24:34 +0200 Subject: [PATCH 718/773] [XrdMacaroons] Fix doxygen warning (mismatch in prototype) --- src/XrdMacaroons/XrdMacaroonsAuthz.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.cc b/src/XrdMacaroons/XrdMacaroonsAuthz.cc index 6c47d476931..df771b06ca6 100644 --- a/src/XrdMacaroons/XrdMacaroonsAuthz.cc +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.cc @@ -21,7 +21,7 @@ namespace { class AuthzCheck { public: - AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t m_max_duration, XrdSysError &log); + AuthzCheck(const char *req_path, const Access_Operation req_oper, ssize_t max_duration, XrdSysError &log); const std::string &GetSecName() const {return m_sec_name;} const std::string &GetErrorMessage() const {return m_emsg;} From a31149574dc184e193abe27cba0bc74d869f39f7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 Apr 2024 14:32:31 +0200 Subject: [PATCH 719/773] [XrdCl] Use long for dirOffset in IndexRemote This is what telldir returns and seekdir takes as input. --- src/XrdCl/XrdClCopy.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClCopy.cc b/src/XrdCl/XrdClCopy.cc index d0acd37e1ef..f0b2892413a 100644 --- a/src/XrdCl/XrdClCopy.cc +++ b/src/XrdCl/XrdClCopy.cc @@ -408,7 +408,7 @@ void AdjustFileInfo( XrdCpFile *file ) //------------------------------------------------------------------------------ XrdCpFile *IndexRemote( XrdCl::FileSystem *fs, std::string basePath, - uint16_t dirOffset ) + long dirOffset ) { using namespace XrdCl; From 13b5143565e24cb05cb3933573bfec5c3b962336 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 16 Apr 2024 15:10:32 +0200 Subject: [PATCH 720/773] [Tests] Fix XAttrTest for when the response vector is empty Cannot index into an empty vector, so ensure it has the two required elements before doing that. --- 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 cbf02eae078..3db6c2f2b83 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -506,10 +506,11 @@ TEST_F(LocalFileHandlerTest, XAttrTest) std::vector resp; GTEST_ASSERT_XRDST( f.GetXAttr( names, resp ) ); + ASSERT_EQ( resp.size(), 2 ); + GTEST_ASSERT_XRDST( resp[0].status ); GTEST_ASSERT_XRDST( resp[1].status ); - EXPECT_EQ( resp.size(), 2 ); int vid = resp[0].name == "version" ? 0 : 1; int did = vid == 0 ? 1 : 0; EXPECT_EQ( resp[vid].name, std::string("version") ); From d6401db2d8ac72656991770a7e098a345a514949 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 16 Apr 2024 15:13:19 +0200 Subject: [PATCH 721/773] [XrdApps] Fix null pointer dereference when response handler is nullptr At least XrdCl::File::~File() calls Close( nullptr, 0 ); which uses a null handler, so the recorder plugin needs to handle this case correctly. --- src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh b/src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh index c9eb2052b94..821a0631f0c 100644 --- a/src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh +++ b/src/XrdApps/XrdClRecordPlugin/XrdClRecorder.hh @@ -193,7 +193,8 @@ class Recorder: public FilePlugIn { action->RecordResult( status, response ); output.Write( std::move( action ) ); - handler->HandleResponseWithHosts( status, response, hostList ); + if (handler) + handler->HandleResponseWithHosts( status, response, hostList ); delete this; } @@ -207,7 +208,8 @@ class Recorder: public FilePlugIn { action->RecordResult( status, response ); output.Write( std::move( action ) ); - handler->HandleResponse( status, response ); + if (handler) + handler->HandleResponse( status, response ); delete this; } From 1d06f0219489503b940297771beb293b633e1303 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 16 Apr 2024 15:43:05 +0200 Subject: [PATCH 722/773] [XrdCl] Fix null pointer dereference when response handler is nullptr --- src/XrdCl/XrdClFileStateHandler.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/XrdCl/XrdClFileStateHandler.cc b/src/XrdCl/XrdClFileStateHandler.cc index 49873c27008..45d889e31e9 100644 --- a/src/XrdCl/XrdClFileStateHandler.cc +++ b/src/XrdCl/XrdClFileStateHandler.cc @@ -614,7 +614,8 @@ namespace XrdCl::AnyObject *response, XrdCl::HostList *hostList ) { - handler->HandleResponseWithHosts( status, response, hostList ); + if (handler) + handler->HandleResponseWithHosts( status, response, hostList ); } //------------------------------------------------------------------------ @@ -966,8 +967,8 @@ namespace XrdCl { AnyObject *obj = new AnyObject(); obj->Set( new StatInfo( *self->pStatInfo ) ); - handler->HandleResponseWithHosts( new XRootDStatus(), obj, - new HostList() ); + if (handler) + handler->HandleResponseWithHosts( new XRootDStatus(), obj, new HostList() ); return XRootDStatus(); } From 509c4b0decab0cffe0be645df1433f92621d8bf5 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 22 Apr 2024 13:47:07 +0200 Subject: [PATCH 723/773] [XrdHttp] Reset HTTP request scitag during reset Fixes #2243 --- src/XrdHttp/XrdHttpReq.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index b61587118aa..e4498537ca4 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -2811,6 +2811,8 @@ void XrdHttpReq::reset() { fopened = false; final = false; + + mScitag = -1; } void XrdHttpReq::getfhandle() { From 90e2cbad0ed730232fa94ccc15703efbce52691f Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 22 Apr 2024 14:58:01 +0200 Subject: [PATCH 724/773] [XrdCrypto] Avoid some repeated calls of EVP_PKEY_check --- src/XrdCrypto/XrdCryptosslAux.cc | 50 +++++++------------------- src/XrdCrypto/XrdCryptosslRSA.cc | 8 ++--- src/XrdCrypto/XrdCryptosslX509.cc | 60 +++++++++++++------------------ 3 files changed, 40 insertions(+), 78 deletions(-) diff --git a/src/XrdCrypto/XrdCryptosslAux.cc b/src/XrdCrypto/XrdCryptosslAux.cc index 942d86b862d..395ef17ba9f 100644 --- a/src/XrdCrypto/XrdCryptosslAux.cc +++ b/src/XrdCrypto/XrdCryptosslAux.cc @@ -49,32 +49,6 @@ static int gErrVerifyChain = 0; XrdOucTrace *sslTrace = 0; -#if OPENSSL_VERSION_NUMBER < 0x10100000L -static RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey) -{ - if (pkey->type != EVP_PKEY_RSA) { - return NULL; - } - return pkey->pkey.rsa; -} -#endif - -static int XrdCheckRSA (EVP_PKEY *pkey) { - int rc; -#if OPENSSL_VERSION_NUMBER < 0x10101000L - RSA *rsa = EVP_PKEY_get0_RSA(pkey); - if (rsa) - rc = RSA_check_key(rsa); - else - rc = -2; -#else - EVP_PKEY_CTX *ckctx = EVP_PKEY_CTX_new(pkey, 0); - rc = EVP_PKEY_check(ckctx); - EVP_PKEY_CTX_free(ckctx); -#endif - return rc; -} - //____________________________________________________________________________ int XrdCryptosslX509VerifyCB(int ok, X509_STORE_CTX *ctx) { @@ -552,7 +526,7 @@ int XrdCryptosslX509ParseFile(FILE *fcer, DEBUG("found a RSA private key in file " << fname); // We need to complete the key // check all the public keys of the loaded certificates - if (XrdCheckRSA(rsa) == 1) { + if (rsa) { // Loop over the chain certificates XrdCryptoX509 *cert = chain->Begin(); while (cert && cert->Opaque()) { @@ -568,12 +542,12 @@ int XrdCryptosslX509ParseFile(FILE *fcer, #endif EVP_PKEY_free(evpp); if (rc == 1) { - DEBUG("RSA key completed"); - // Update PKI in certificate + // Update PKI in certificate; also tests if the key is complete cert->SetPKI((XrdCryptoX509data)rsa); - // Update status - cert->PKI()->status = XrdCryptoRSA::kComplete; - break; + if (cert->PKI()->status == XrdCryptoRSA::kComplete) { + DEBUG("RSA key completed"); + break; + } } } } @@ -662,7 +636,7 @@ int XrdCryptosslX509ParseBucket(XrdSutBucket *b, XrdCryptoX509Chain *chain) DEBUG("found a RSA private key in bucket"); // We need to complete the key // check all the public keys of the loaded certificates - if (XrdCheckRSA(rsa) == 1) { + if (rsa) { // Loop over the chain certificates XrdCryptoX509 *cert = chain->Begin(); while (cert && cert->Opaque()) { @@ -678,12 +652,12 @@ int XrdCryptosslX509ParseBucket(XrdSutBucket *b, XrdCryptoX509Chain *chain) #endif EVP_PKEY_free(evpp); if (rc == 1) { - DEBUG("RSA key completed"); - // Update PKI in certificate + // Update PKI in certificate; also tests if the key is complete cert->SetPKI((XrdCryptoX509data)rsa); - // Update status - cert->PKI()->status = XrdCryptoRSA::kComplete; - break; + if (cert->PKI()->status == XrdCryptoRSA::kComplete) { + DEBUG("RSA key completed"); + break; + } } } } diff --git a/src/XrdCrypto/XrdCryptosslRSA.cc b/src/XrdCrypto/XrdCryptosslRSA.cc index ae49406d61d..5e37ea7852e 100644 --- a/src/XrdCrypto/XrdCryptosslRSA.cc +++ b/src/XrdCrypto/XrdCryptosslRSA.cc @@ -236,10 +236,10 @@ XrdCryptosslRSA::XrdCryptosslRSA(const XrdCryptosslRSA &r) : XrdCryptoRSA() } } else { if ((fEVP = PEM_read_bio_PrivateKey(bcpy,0,0,0))) { - // Check consistency - if (XrdCheckRSA(fEVP) == 1) { - // Update status - status = kComplete; + // Check consistency only if original was not marked complete + if (r.status == kComplete || XrdCheckRSA(fEVP) == 1) { + // Update status + status = kComplete; } } } diff --git a/src/XrdCrypto/XrdCryptosslX509.cc b/src/XrdCrypto/XrdCryptosslX509.cc index ec844b125cf..01fbdb25389 100644 --- a/src/XrdCrypto/XrdCryptosslX509.cc +++ b/src/XrdCrypto/XrdCryptosslX509.cc @@ -35,6 +35,7 @@ #include #include #include +#include #include "XrdCrypto/XrdCryptosslRSA.hh" #include "XrdCrypto/XrdCryptosslX509.hh" @@ -43,32 +44,6 @@ #include -#if OPENSSL_VERSION_NUMBER < 0x10100000L -static RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey) -{ - if (pkey->type != EVP_PKEY_RSA) { - return NULL; - } - return pkey->pkey.rsa; -} -#endif - -static int XrdCheckRSA (EVP_PKEY *pkey) { - int rc; -#if OPENSSL_VERSION_NUMBER < 0x10101000L - RSA *rsa = EVP_PKEY_get0_RSA(pkey); - if (rsa) - rc = RSA_check_key(rsa); - else - rc = -2; -#else - EVP_PKEY_CTX *ckctx = EVP_PKEY_CTX_new(pkey, 0); - rc = EVP_PKEY_check(ckctx); - EVP_PKEY_CTX_free(ckctx); -#endif - return rc; -} - #define BIO_PRINT(b,c) \ BUF_MEM *bptr; \ BIO_get_mem_ptr(b, &bptr); \ @@ -175,9 +150,10 @@ XrdCryptosslX509::XrdCryptosslX509(const char *cf, const char *kf) if ((evpp = PEM_read_PrivateKey(fk,0,0,0))) { DEBUG("RSA key completed "); // Test consistency - if (XrdCheckRSA(evpp) == 1) { + auto tmprsa = std::make_unique(evpp, 1); + if (tmprsa->status == XrdCryptoRSA::kComplete) { // Save it in pki - pki = new XrdCryptosslRSA(evpp); + pki = tmprsa.release(); } } else { DEBUG("cannot read the key from file"); @@ -432,14 +408,26 @@ void XrdCryptosslX509::CertType() //_____________________________________________________________________________ void XrdCryptosslX509::SetPKI(XrdCryptoX509data newpki) { - // Set PKI - - // Cleanup key first - if (pki) - delete pki; - if (newpki) - pki = new XrdCryptosslRSA((EVP_PKEY *)newpki, 1); - + // SetPKI: + // if newpki is null does nothing + // if newpki contains a consistent private & public key we take ownership + // so that this->PKI()->status will be kComplete. + // otherwise, newpki is not consistent: + // if the previous PKI() was null or was already kComplete it is and reset + // so that this->PKI()->status will be kInvalid. + + if (!newpki) return; + + auto tmprsa = std::make_unique((EVP_PKEY*)newpki, 1); + if (!pki || pki->status == XrdCryptoRSA::kComplete || + tmprsa->status == XrdCryptoRSA::kComplete) { + // Cleanup any existing key first + if (pki) + delete pki; + + // Set PKI + pki = tmprsa.release(); + } } //_____________________________________________________________________________ From 433cc8580b17448ab818f5dbe23d5bd8121b5f21 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 29 Apr 2024 16:52:45 +0200 Subject: [PATCH 725/773] [XrdHttp] External handlers can be loaded without TLS The XrootD server administrator can now configure HTTP handlers without having configured TLS. They can do so by adding "notls" after the path of the library. Example: http.exthandler xrdtpc libXrdHttpTPC.so notls By default, HTTP external handlers can only be loaded if TLS is enabled. --- src/XrdHttp/XrdHttpProtocol.cc | 33 +++++++++++++++++++++++++++++---- src/XrdHttp/XrdHttpProtocol.hh | 11 +++++++---- src/XrdTpc/XrdTpcConfigure.cc | 3 ++- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index ad0a282071a..71bf1d61e1b 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1105,6 +1105,7 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { eDest.Say("Config warning: HTTPS functionality ", why); httpsmode = hsmOff; + LoadExtHandlerNoTls(extHIVec, ConfigFN, *myEnv); if (what) {eDest.Say("Config failure: ", what, " HTTPS but it ", why); NoGo = 1; @@ -2573,6 +2574,8 @@ int XrdHttpProtocol::xexthandler(XrdOucStream &Config, std::vector &hiVec) { char *val, path[1024], namebuf[1024]; char *parm; + // By default, every external handler need TLS configured to be loaded + bool noTlsOK = false; // Get the name // @@ -2601,6 +2604,12 @@ int XrdHttpProtocol::xexthandler(XrdOucStream &Config, } strcpy(path, val); + + val = Config.GetWord(); + + if(val && !strcmp("notls",val)) { + noTlsOK = true; + } // Everything else is a free string // @@ -2626,7 +2635,7 @@ int XrdHttpProtocol::xexthandler(XrdOucStream &Config, // Create an info struct and push it on the list of ext handlers to load // - hiVec.push_back(extHInfo(namebuf, path, (parm ? parm : ""))); + hiVec.push_back(extHInfo(namebuf, path, (parm ? parm : ""), noTlsOK)); return 0; } @@ -2926,11 +2935,23 @@ int XrdHttpProtocol::LoadSecXtractor(XrdSysError *myeDest, const char *libName, myLib.Unload(); return 1; } - /******************************************************************************/ /* L o a d E x t H a n d l e r */ /******************************************************************************/ +int XrdHttpProtocol::LoadExtHandlerNoTls(std::vector &hiVec, const char *cFN, XrdOucEnv &myEnv) { + for (int i = 0; i < (int) hiVec.size(); i++) { + if(hiVec[i].extHNoTlsOK) { + // The external plugin does not need TLS to be loaded + if (LoadExtHandler(&eDest, hiVec[i].extHPath.c_str(), cFN, + hiVec[i].extHParm.c_str(), &myEnv, + hiVec[i].extHName.c_str())) + return 1; + } + } + return 0; +} + int XrdHttpProtocol::LoadExtHandler(std::vector &hiVec, const char *cFN, XrdOucEnv &myEnv) { @@ -2943,11 +2964,15 @@ int XrdHttpProtocol::LoadExtHandler(std::vector &hiVec, // Load all of the specified external handlers. // - for (int i = 0; i < (int)hiVec.size(); i++) + for (int i = 0; i < (int)hiVec.size(); i++) { + // Only load the external handlers that were not already loaded + // by LoadExtHandlerNoTls(...) + if(!ExtHandlerLoaded(hiVec[i].extHName.c_str())) { if (LoadExtHandler(&eDest, hiVec[i].extHPath.c_str(), cFN, hiVec[i].extHParm.c_str(), &myEnv, hiVec[i].extHName.c_str())) return 1; - + } + } return 0; } diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index 9d476480037..ab9560a0dad 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -190,9 +190,9 @@ private: {XrdOucString extHName; // The instance name (1 to 16 characters) XrdOucString extHPath; // The shared library path XrdOucString extHParm; // The parameter (sort of) - - extHInfo(const char *hName, const char *hPath, const char *hParm) - : extHName(hName), extHPath(hPath), extHParm(hParm) {} + bool extHNoTlsOK; // If true the exthandler can be loaded if TLS has NOT been configured + extHInfo(const char *hName, const char *hPath, const char *hParm, const bool hNoTlsOK) + : extHName(hName), extHPath(hPath), extHParm(hParm), extHNoTlsOK(hNoTlsOK) {} ~extHInfo() {} }; /// Functions related to the configuration @@ -235,7 +235,10 @@ private: XrdHttpExtHandler *ptr; } exthandler[MAX_XRDHTTPEXTHANDLERS]; static int exthandlercnt; - + + static int LoadExtHandlerNoTls(std::vector &hiVec, + const char *cFN, XrdOucEnv &myEnv); + // Loads the ExtHandler plugin, if available static int LoadExtHandler(std::vector &hiVec, const char *cFN, XrdOucEnv &myEnv); diff --git a/src/XrdTpc/XrdTpcConfigure.cc b/src/XrdTpc/XrdTpcConfigure.cc index 55747b1b33f..c77502ff5ab 100644 --- a/src/XrdTpc/XrdTpcConfigure.cc +++ b/src/XrdTpc/XrdTpcConfigure.cc @@ -107,9 +107,10 @@ bool TPCHandler::Configure(const char *configfn, XrdOucEnv *myEnv) if ((cafile = myEnv->Get("http.cafile"))) { m_cafile = cafile; } + if (!cadir && !cafile) { + // We do not necessary need TLS to perform HTTP TPC transfers, just log that these values were not specified m_log.Emsg("Config", "neither xrd.tls cadir nor certfile value specified; is TLS enabled?"); - return false; } void *sfs_raw_ptr; From 25ef24cb6d517caa7baf29946f3cc00b106c935c Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 30 Apr 2024 13:45:13 +0200 Subject: [PATCH 726/773] [XrdHttp] Changed the configuration parameter for non-tls exthandler This option is now called "+notls" and can be provided after the name of the external handler. Example: http.exthandler httptpc +notls libXrdHttpTPC.so --- src/XrdHttp/XrdHttpProtocol.cc | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 71bf1d61e1b..4f9d8594d0b 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -2590,10 +2590,17 @@ int XrdHttpProtocol::xexthandler(XrdOucStream &Config, } strncpy(namebuf, val, sizeof(namebuf)); namebuf[ sizeof(namebuf)-1 ] = '\0'; - + + // Get the +notls option if it was provided + val = Config.GetWord(); + + if(val && !strcmp("+notls",val)) { + noTlsOK = true; + val = Config.GetWord(); + } + // Get the path // - val = Config.GetWord(); if (!val || !val[0]) { eDest.Emsg("Config", "No http external handler plugin specified."); return 1; @@ -2604,12 +2611,6 @@ int XrdHttpProtocol::xexthandler(XrdOucStream &Config, } strcpy(path, val); - - val = Config.GetWord(); - - if(val && !strcmp("notls",val)) { - noTlsOK = true; - } // Everything else is a free string // From d401dc1beb8b8efbeed105407a0c4f151c23214b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 25 Apr 2024 12:02:13 +0200 Subject: [PATCH 727/773] [XrdSys] Fix warning about unused parameter --- src/XrdSys/XrdSysPthread.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdSys/XrdSysPthread.hh b/src/XrdSys/XrdSysPthread.hh index 6d980246094..c5a6a9be96f 100644 --- a/src/XrdSys/XrdSysPthread.hh +++ b/src/XrdSys/XrdSysPthread.hh @@ -349,7 +349,7 @@ inline void UnLock() {pthread_rwlock_unlock(&lock);} enum PrefType {prefWR=1}; - XrdSysRWLock(PrefType ptype) + XrdSysRWLock(PrefType /* ptype */) { #if defined(__linux__) && (defined(__GLIBC__) || defined(__UCLIBC__)) pthread_rwlockattr_t attr; @@ -364,7 +364,7 @@ enum PrefType {prefWR=1}; XrdSysRWLock() {pthread_rwlock_init(&lock, NULL);} ~XrdSysRWLock() {pthread_rwlock_destroy(&lock);} -inline void ReInitialize(PrefType ptype) +inline void ReInitialize(PrefType /* ptype */) { pthread_rwlock_destroy(&lock); #if defined(__linux__) && (defined(__GLIBC__) || defined(__UCLIBC__)) From afd7b7b407954866295ad1c58ef3083e0b9a98af Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 May 2024 11:52:16 +0200 Subject: [PATCH 728/773] [CI]: Run on macOS 13 while until reverse lookup is fixed on 14 Issue: https://github.com/actions/runner-images/issues/8649 --- .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 434369197e9..d640cd6dd96 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -270,7 +270,7 @@ jobs: macos: name: macOS - runs-on: macos-latest + runs-on: macos-13 env: CC: clang From 52214472a4d89a30dc1a2489850edd9665df5dce Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Fri, 16 Feb 2024 12:04:10 +0100 Subject: [PATCH 729/773] [XrdTpcTPC] PMark - Connect curl socket at socket creation This allows to improve HTTP-TPC transfers packet marking precision. If the packet marking is enabled and the client submitted a request with a SciTag header, then the connection of the socket will be done within a timeout of 60 seconds. The TPC transfer will fail if the socket could not be connected. --- src/XrdNet/XrdNetPMarkFF.cc | 6 ++- src/XrdNet/XrdNetUtils.cc | 80 ++++++++++++++++++++++++++++++++ src/XrdNet/XrdNetUtils.hh | 5 ++ src/XrdTpc/XrdTpcMultistream.cc | 2 +- src/XrdTpc/XrdTpcPMarkManager.cc | 32 ++++++++++--- src/XrdTpc/XrdTpcPMarkManager.hh | 48 +++++++++++++------ src/XrdTpc/XrdTpcTPC.cc | 40 ++++++++++------ src/XrdTpc/XrdTpcTPC.hh | 12 +++-- 8 files changed, 185 insertions(+), 40 deletions(-) diff --git a/src/XrdNet/XrdNetPMarkFF.cc b/src/XrdNet/XrdNetPMarkFF.cc index 6ed96d68aab..5bed2f7823a 100644 --- a/src/XrdNet/XrdNetPMarkFF.cc +++ b/src/XrdNet/XrdNetPMarkFF.cc @@ -290,6 +290,7 @@ void XrdNetPMarkFF::SockStats(struct sockStats &ss) #ifndef __linux__ memset(&ss, 0, sizeof(struct sockStats)); #else + EPName("SockStats"); struct tcp_info tcpInfo; socklen_t tiLen = sizeof(tcpInfo); @@ -298,7 +299,10 @@ void XrdNetPMarkFF::SockStats(struct sockStats &ss) ss.bSent = static_cast(tcpInfo.tcpi_bytes_acked); ss.msRTT = static_cast(tcpInfo.tcpi_rtt/1000); ss.usRTT = static_cast(tcpInfo.tcpi_rtt%1000); - } else memset(&ss, 0, sizeof(struct sockStats)); + } else { + memset(&ss, 0, sizeof(struct sockStats)); + DEBUG("Unable to get TCP information errno=" << strerror(errno)); + } #endif } diff --git a/src/XrdNet/XrdNetUtils.cc b/src/XrdNet/XrdNetUtils.cc index fbc6c5ff714..d8bc6947955 100644 --- a/src/XrdNet/XrdNetUtils.cc +++ b/src/XrdNet/XrdNetUtils.cc @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include "XrdNet/XrdNetAddr.hh" #include "XrdNet/XrdNetIdentity.hh" @@ -938,3 +940,81 @@ bool XrdNetUtils::Singleton(const char *hSpec, const char **eText) // return isSingle; } + +bool XrdNetUtils::ConnectWithTimeout(int sockfd, const struct sockaddr* clientAddr,size_t clientAddrLen, uint32_t timeout_sec, std::stringstream &errMsg) { + + if (!SetSockBlocking(sockfd, false, errMsg)) { // Set to non-blocking + close(sockfd); + return false; + } + + int result = connect(sockfd, clientAddr, clientAddrLen); + if (result == 0) { + //We've managed to connect immediately + // Set the socket back to blocking + if(!SetSockBlocking(sockfd, true, errMsg)) { + ::close(sockfd); + return false; + } + return true; + } else if (errno != EINPROGRESS) { + errMsg << "Connection failed: " << strerror(errno); + ::close(sockfd); + return false; + } + + struct pollfd fds; + fds.fd = sockfd; + fds.events = POLLOUT; // Wait for the socket to be ready to write + + result = poll(&fds, 1, timeout_sec * 1000); //Timeout in milliseconds + + if (result < 0) { + errMsg << "Poll error: " << strerror(errno); + ::close(sockfd); + return false; + } else if (result == 0) { + errMsg << "Connection timed out"; + ::close(sockfd); + return false; + } + // Polling succeeded + if (!(fds.revents & POLLOUT)) { + // We should normally not reach this code + errMsg << "Poll returned without error but the corresponding socket (" << sockfd << ") is not ready to write"; + ::close(sockfd); + return false; + } + // Check for potential errors on the socket after polling + int so_error; + socklen_t len = sizeof(so_error); + getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len); + if (so_error != 0) { + errMsg << "Connection failed after poll: " << strerror(so_error); + ::close(sockfd); + return false; + } + // Everything succeeded, set the socket back to blocking + if(!SetSockBlocking(sockfd, true, errMsg)) { + ::close(sockfd); + return false; + } + return true; +} + +bool XrdNetUtils::SetSockBlocking(int sockfd, bool blocking, std::stringstream & errMsg) { + int flags = fcntl(sockfd, F_GETFL, 0); + if (flags == -1) { + errMsg << "Failed to get socket flags " << strerror(errno); + return false; + } + + flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); + + if (fcntl(sockfd, F_SETFL, flags) == -1) { + errMsg << "Failed to set socket blocking/non-blocking " << strerror(errno); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/XrdNet/XrdNetUtils.hh b/src/XrdNet/XrdNetUtils.hh index 6a3d4ba4036..bb92ac94cd7 100644 --- a/src/XrdNet/XrdNetUtils.hh +++ b/src/XrdNet/XrdNetUtils.hh @@ -32,6 +32,8 @@ #include #include +#include +#include #include "XrdOuc/XrdOucEnum.hh" @@ -425,6 +427,8 @@ static int SetAuto(AddrOpts aOpts=allIPMap); static bool Singleton(const char *hSpec, const char **eText=0); +static bool ConnectWithTimeout(int sockfd, const struct sockaddr* clientAddr, size_t clientAddrLen,uint32_t timeout_sec, std::stringstream & errMsg); + //------------------------------------------------------------------------------ //! Constructor //------------------------------------------------------------------------------ @@ -448,6 +452,7 @@ const char *GetHostPort(XrdNetSpace::hpSpec &aBuff, const char *hSpec, int pNum) static const char *getMyFQN(const char *&myDom); static int setET(const char **errtxt, int rc); +static bool SetSockBlocking(int sockfd, bool blocking, std::stringstream & errMsg); static int autoFamily; static int autoHints; }; diff --git a/src/XrdTpc/XrdTpcMultistream.cc b/src/XrdTpc/XrdTpcMultistream.cc index aefc729383e..0090b0ae30f 100644 --- a/src/XrdTpc/XrdTpcMultistream.cc +++ b/src/XrdTpc/XrdTpcMultistream.cc @@ -282,7 +282,7 @@ int TPCHandler::RunCurlWithStreamsImpl(XrdHttpExtReq &req, State &state, } // Notify the packet marking manager that the transfer will start after this point - rec.pmarkManager.startTransfer(&req); + rec.pmarkManager.startTransfer(); // Create the multi-handle and add in the current transfer to it. MultiCurlHandler mch(handles, m_log); diff --git a/src/XrdTpc/XrdTpcPMarkManager.cc b/src/XrdTpc/XrdTpcPMarkManager.cc index 4d99bd6621a..1f8619032e6 100644 --- a/src/XrdTpc/XrdTpcPMarkManager.cc +++ b/src/XrdTpc/XrdTpcPMarkManager.cc @@ -22,6 +22,7 @@ #include #include "XrdTpcPMarkManager.hh" +#include "XrdNet/XrdNetUtils.hh" namespace XrdTpc { @@ -30,17 +31,34 @@ PMarkManager::SocketInfo::SocketInfo(int fd, const struct sockaddr * sockP) { client.addrInfo = static_cast(&netAddr); } -PMarkManager::PMarkManager(XrdNetPMark *pmark) : mPmark(pmark), mTransferWillStart(false) {} +PMarkManager::PMarkManager(XrdHttpExtReq & req) : mPmark(req.pmark), mReq(req), mTransferWillStart(false) {} void PMarkManager::addFd(int fd, const struct sockaddr * sockP) { - if(mPmark && mTransferWillStart && mReq->mSciTag >= 0) { + if(isEnabled() && mTransferWillStart) { // 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; +bool PMarkManager::connect(int fd, const struct sockaddr *sockP, size_t sockPLen, uint32_t timeout_sec, std::stringstream &err) { + if(isEnabled()) { + // We only connect if the packet marking is enabled + bool couldConnect = XrdNetUtils::ConnectWithTimeout(fd,sockP,sockPLen,timeout_sec,err); + if(couldConnect) { + addFd(fd,sockP); + } else { + return false; + } + } + // If pmark is not enabled, we leave libcurl doing the connection + return true; +} + +bool PMarkManager::isEnabled() const { + return mPmark && (mReq.mSciTag >= 0); +} + +void PMarkManager::startTransfer() { mTransferWillStart = true; } @@ -52,9 +70,9 @@ void PMarkManager::beginPMarks() { if(mPmarkHandles.empty()) { // Create the first pmark handle std::stringstream ss; - ss << "scitag.flow=" << mReq->mSciTag; + ss << "scitag.flow=" << mReq.mSciTag; SocketInfo & sockInfo = mSocketInfos.front(); - auto pmark = mPmark->Begin(sockInfo.client, mReq->resource.c_str(), ss.str().c_str(), "http-tpc"); + auto pmark = mPmark->Begin(sockInfo.client, mReq.resource.c_str(), ss.str().c_str(), "http-tpc"); if(!pmark) { return; } @@ -72,7 +90,7 @@ void PMarkManager::beginPMarks() { } int fd = sockInfo.client.addrInfo->SockFD(); - mPmarkHandles.emplace(fd, std::move(std::unique_ptr(pmark))); + mPmarkHandles.emplace(fd, std::unique_ptr(pmark)); mSocketInfos.pop(); } } diff --git a/src/XrdTpc/XrdTpcPMarkManager.hh b/src/XrdTpc/XrdTpcPMarkManager.hh index 938de5c62d8..e7885d03808 100644 --- a/src/XrdTpc/XrdTpcPMarkManager.hh +++ b/src/XrdTpc/XrdTpcPMarkManager.hh @@ -35,7 +35,7 @@ * 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. + * will do the connection and 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. @@ -62,24 +62,36 @@ public: XrdSecEntity client; }; - PMarkManager(XrdNetPMark * pmark); + PMarkManager(XrdHttpExtReq & req); + /** - * 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 + * Will connect the socket attached to the file descriptor within a certain timeout and add the file descriptor to the. + * packet marking manager so packet marking can be done achieved later on + * If pmark is not enabled, this function will just return true without trying to connect (libcurl will + * perform the connection). False will be returned in case the connection could not have been done. + * + * @param fd the fd associated to the socket + * @param sockP the connection information + * @param sockPLen the length of the connection information + * @param timeout_sec the timeout after which the connection is considered failed + * @param err the error stream allowing to understand from where the issue comes from + * @return true if the connection could be established or if pmark is disabled, false otherwise */ - void addFd(int fd, const struct sockaddr * sockP); + bool connect(int fd, const struct sockaddr * sockP, size_t sockPLen, uint32_t timeout_sec, std::stringstream & err); + + /** + * @return true if packet marking can happen i.e the packet marking is enabled in + * the configuration and a scitag was provided by the user. + */ + bool isEnabled() const; /** * 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); + void startTransfer(); + /** * Creates the different packet marking handles allowing to mark the transfer packets * @@ -97,16 +109,26 @@ public: virtual ~PMarkManager() = default; private: + /** + * 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); + // 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; + // The XrdHttpTPC request information + XrdHttpExtReq & mReq; // Is true when startTransfer(...) has been called bool mTransferWillStart; - // The XrdHttpTPC request information - XrdHttpExtReq * mReq; }; } // namespace XrdTpc diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 7869d236652..c589c7f6fb8 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -95,16 +95,14 @@ void CurlDeleter::operator()(CURL *curl) * was needed for monitoring to report what IP protocol was being used. * It has been kept in case we will need this callback in the future. */ -int TPCHandler::sockopt_setcloexec_callback(void *clientp, curl_socket_t curlfd, curlsocktype purpose) { - int oldFlags = fcntl(curlfd,F_GETFD,0); - if(oldFlags < 0) { - return CURL_SOCKOPT_ERROR; - } - oldFlags |= FD_CLOEXEC; - if(!fcntl(curlfd,F_SETFD,oldFlags)) { - return CURL_SOCKOPT_OK; - } - return CURL_SOCKOPT_ERROR; +int TPCHandler::sockopt_callback(void *clientp, curl_socket_t curlfd, curlsocktype purpose) { + TPCLogRecord * rec = (TPCLogRecord *)clientp; + if (purpose == CURLSOCKTYPE_IPCXN && rec && rec->pmarkManager.isEnabled()) { + // We will not reach this callback if the corresponding socket could not have been connected + // the socket is already connected only if the packet marking is enabled + return CURL_SOCKOPT_ALREADY_CONNECTED; + } + return CURL_SOCKOPT_OK; } /******************************************************************************/ @@ -132,8 +130,12 @@ int TPCHandler::opensocket_callback(void *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); + std::stringstream connectErrMsg; + + if(!rec->pmarkManager.connect(fd, &(aInfo->addr), aInfo->addrlen, CONNECT_TIMEOUT, connectErrMsg)) { + rec->m_log->Emsg(rec->log_prefix.c_str(),"Unable to connect socket:", connectErrMsg.str().c_str()); + return CURL_SOCKET_BAD; + } } return fd; @@ -668,7 +670,7 @@ 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); + rec.pmarkManager.startTransfer(); mres = curl_multi_perform(multi_handle, &running_handles); if (mres == CURLM_CALL_MULTI_PERFORM) { // curl_multi_perform should be called again immediately. On newer @@ -855,10 +857,11 @@ int TPCHandler::RunCurlBasic(CURL *curl, XrdHttpExtReq &req, State &state, /******************************************************************************/ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) { - TPCLogRecord rec(req.pmark); + TPCLogRecord rec(req); rec.log_prefix = "PushRequest"; rec.local = req.resource; rec.remote = resource; + rec.m_log = &m_log; char *name = req.GetSecEntity().name; req.GetClientID(rec.clID); if (name) rec.name = name; @@ -875,10 +878,13 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1); // 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_SOCKOPTFUNCTION, sockopt_callback); curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT); auto query_header = req.headers.find("xrd-http-fullresource"); std::string redirect_resource = req.resource; if (query_header != req.headers.end()) { @@ -938,10 +944,11 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) /******************************************************************************/ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) { - TPCLogRecord rec(req.pmark); + TPCLogRecord rec(req); rec.log_prefix = "PullRequest"; rec.local = req.resource; rec.remote = resource; + rec.m_log = &m_log; char *name = req.GetSecEntity().name; req.GetClientID(rec.clID); if (name) rec.name = name; @@ -992,8 +999,11 @@ 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_SOCKOPTFUNCTION, sockopt_callback); + curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA , &rec); curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback); curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT); 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 d62ce9379fc..7c4515d5317 100644 --- a/src/XrdTpc/XrdTpcTPC.hh +++ b/src/XrdTpc/XrdTpcTPC.hh @@ -51,7 +51,7 @@ public: private: - static int sockopt_setcloexec_callback(void * clientp, curl_socket_t curlfd, curlsocktype purpose); + static int sockopt_callback(void * clientp, curl_socket_t curlfd, curlsocktype purpose); static int opensocket_callback(void *clientp, curlsocktype purpose, struct curl_sockaddr *address); @@ -60,8 +60,8 @@ private: struct TPCLogRecord { - TPCLogRecord(XrdNetPMark * pmark) : bytes_transferred( -1 ), status( -1 ), - tpc_status(-1), streams( 1 ), isIPv6(false), pmarkManager(pmark) + TPCLogRecord(XrdHttpExtReq & req) : bytes_transferred( -1 ), status( -1 ), + tpc_status(-1), streams( 1 ), isIPv6(false), mReq(req), pmarkManager(mReq) { gettimeofday(&begT, 0); // Set effective start time } @@ -79,7 +79,9 @@ private: int tpc_status; unsigned int streams; bool isIPv6; + XrdHttpExtReq & mReq; XrdTpc::PMarkManager pmarkManager; + XrdSysError * m_log; }; int ProcessOptionsReq(XrdHttpExtReq &req); @@ -167,5 +169,9 @@ private: #endif bool usingEC; // indicate if XrdEC is used + + // Time to connect the curl socket to the remote server uses the linux's default value + // of 60 seconds + static const long CONNECT_TIMEOUT = 60; }; } From fa118b76117eda85e9310029beb287f231bd04f6 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 2 May 2024 17:55:27 +0200 Subject: [PATCH 730/773] [XrdHttp] Improved first-line and header parsing When we detect the end of header in the client request ("\r\n"), we will actually continue to read data from the buffer if there are still some to read. --- src/XrdHttp/XrdHttpProtocol.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 4f9d8594d0b..7d0bcae012f 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -616,6 +616,19 @@ int XrdHttpProtocol::Process(XrdLink *lp) // We ignore the argument here TRACE(DEBUG, " rc:" << rc << " got hdr line: " << tmpline.c_str()); if ((rc == 2) && (tmpline.length() > 1) && (tmpline[rc - 1] == '\n')) { + // A \r\n was detected + if(BuffUsed()) { + if(CurrentReq.request == CurrentReq.rtUnset){ + // The first line has a "\n" at the beginning + // Bad request + return SendSimpleResp(400, nullptr, nullptr, "Got first line break before the HTTP verb", 0, false); + } else { + // We are getting the headers from the buffer and we detected the header end. + // Though, there is still data to read from the buffer, continue to read! + continue; + } + } + // We have detected the header end and no data remains in the buffer, we stop this loop CurrentReq.headerok = true; TRACE(DEBUG, " rc:" << rc << " detected header end."); break; From d09136e2cf8d1aac82777e6596ab7e60615a89b2 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 2 Apr 2024 17:25:46 +0200 Subject: [PATCH 731/773] [XrdHttp] Add new option to allow for tpc unrestricted redirection Solves issue #2228 --- src/XrdHttp/XrdHttpExtHandler.cc | 2 ++ src/XrdHttp/XrdHttpExtHandler.hh | 2 ++ src/XrdHttp/XrdHttpProtocol.cc | 24 +++++++++++++- src/XrdHttp/XrdHttpProtocol.hh | 4 +++ src/XrdTpc/XrdTpcConfigure.cc | 1 + src/XrdTpc/XrdTpcState.cc | 6 +++- src/XrdTpc/XrdTpcState.hh | 55 +++++++++++++++++--------------- src/XrdTpc/XrdTpcTPC.cc | 6 ++-- 8 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/XrdHttp/XrdHttpExtHandler.cc b/src/XrdHttp/XrdHttpExtHandler.cc index 674620acaae..caa77fa9c86 100644 --- a/src/XrdHttp/XrdHttpExtHandler.cc +++ b/src/XrdHttp/XrdHttpExtHandler.cc @@ -117,5 +117,7 @@ verb(req->requestverb), headers(req->allheaders) { pmark = prot->pmarkHandle; mSciTag = req->mScitag; + tpcForwardCreds = prot->tpcForwardCreds; + length = req->length; } diff --git a/src/XrdHttp/XrdHttpExtHandler.hh b/src/XrdHttp/XrdHttpExtHandler.hh index 06fd468ac07..60ea711d317 100644 --- a/src/XrdHttp/XrdHttpExtHandler.hh +++ b/src/XrdHttp/XrdHttpExtHandler.hh @@ -59,6 +59,8 @@ public: XrdNetPMark * pmark; + bool tpcForwardCreds = false; + int mSciTag; // Get full client identifier diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 7d0bcae012f..0004cdee23d 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -112,6 +112,7 @@ char *XrdHttpProtocol::xrd_cslist = nullptr; XrdNetPMark * XrdHttpProtocol::pmarkHandle = nullptr; XrdHttpChecksumHandler XrdHttpProtocol::cksumHandler = XrdHttpChecksumHandler(); XrdHttpReadRangeHandler::Configuration XrdHttpProtocol::ReadRangeConfig; +bool XrdHttpProtocol::tpcForwardCreds = false; XrdSysTrace XrdHttpTrace("http"); @@ -1078,6 +1079,7 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { else if TS_Xeq("header2cgi", xheader2cgi); else if TS_Xeq("httpsmode", xhttpsmode); else if TS_Xeq("tlsreuse", xtlsreuse); + else if TS_Xeq("auth", xauth); else { eDest.Say("Config warning: ignoring unknown directive '", var, "'."); Config.Echo(); @@ -2827,7 +2829,27 @@ int XrdHttpProtocol::xtlsreuse(XrdOucStream & Config) { eDest.Emsg("config", "invalid tlsreuse parameter -", val); return 1; } - + +int XrdHttpProtocol::xauth(XrdOucStream &Config) { + char *val = Config.GetWord(); + if(val) { + if(!strcmp("tpc",val)) { + if(!(val = Config.GetWord())) { + eDest.Emsg("Config", "http.auth tpc value not specified."); return 1; + } else { + if(!strcmp("fcreds",val)) { + tpcForwardCreds = true; + } else { + eDest.Emsg("Config", "http.auth tpc value is invalid"); return 1; + } + } + } else { + eDest.Emsg("Config", "http.auth value is invalid"); return 1; + } + } + return 0; +} + /******************************************************************************/ /* x t r a c e */ /******************************************************************************/ diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index ab9560a0dad..88d65827dca 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -219,6 +219,7 @@ private: static int xheader2cgi(XrdOucStream &Config); static int xhttpsmode(XrdOucStream &Config); static int xtlsreuse(XrdOucStream &Config); + static int xauth(XrdOucStream &Config); static bool isRequiredXtractor; // If true treat secxtractor errors as fatal static XrdHttpSecXtractor *secxtractor; @@ -443,5 +444,8 @@ protected: /// Packet marking handler pointer (assigned from the environment during the Config() call) static XrdNetPMark * pmarkHandle; + + /// If set to true, the HTTP TPC transfers will forward the credentials to redirected hosts + static bool tpcForwardCreds; }; #endif diff --git a/src/XrdTpc/XrdTpcConfigure.cc b/src/XrdTpc/XrdTpcConfigure.cc index c77502ff5ab..00c19e128c8 100644 --- a/src/XrdTpc/XrdTpcConfigure.cc +++ b/src/XrdTpc/XrdTpcConfigure.cc @@ -73,6 +73,7 @@ bool TPCHandler::Configure(const char *configfn, XrdOucEnv *myEnv) } } else if (!strcmp("tpc.timeout", val)) { if (!(val = Config.GetWord())) { + Config.Close(); m_log.Emsg("Config","tpc.timeout value not specified."); return false; } if (XrdOuca2x::a2tm(m_log, "timeout value", val, &m_timeout, 0)) return false; diff --git a/src/XrdTpc/XrdTpcState.cc b/src/XrdTpc/XrdTpcState.cc index f5412752401..f0b61902ac4 100644 --- a/src/XrdTpc/XrdTpcState.cc +++ b/src/XrdTpc/XrdTpcState.cc @@ -47,6 +47,7 @@ void State::Move(State &other) curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); } } + tpcForwardCreds = other.tpcForwardCreds; other.m_headers_copy.clear(); other.m_curl = NULL; other.m_headers = NULL; @@ -73,6 +74,9 @@ bool State::InstallHandlers(CURL *curl) { } } curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + if(tpcForwardCreds) { + curl_easy_setopt(curl,CURLOPT_UNRESTRICTED_AUTH,1L); + } // Only use low-speed limits with libcurl v7.38 or later. // Older versions have poor transfer performance, corrected in curl commit cacdc27f. @@ -241,7 +245,7 @@ State *State::Duplicate() { throw std::runtime_error("Failed to duplicate existing curl handle."); } - State *state = new State(0, *m_stream, curl, m_push); + State *state = new State(0, *m_stream, curl, m_push, tpcForwardCreds); if (m_headers) { state->m_headers_copy.reserve(m_headers_copy.size()); diff --git a/src/XrdTpc/XrdTpcState.hh b/src/XrdTpc/XrdTpcState.hh index 664ef46a3f6..b00375bbf62 100644 --- a/src/XrdTpc/XrdTpcState.hh +++ b/src/XrdTpc/XrdTpcState.hh @@ -39,19 +39,20 @@ public: * Don't use that constructor if you want to do some transfers. * @param curl the curl handle */ - State(CURL * curl): - m_push(true), - m_recv_status_line(false), - m_recv_all_headers(false), - m_offset(0), - m_start_offset(0), - m_status_code(-1), - m_error_code(0), - m_content_length(-1), - m_stream(NULL), - m_curl(curl), - m_headers(NULL), - m_is_transfer_state(false) + State(CURL * curl, bool tpcForwardCreds): + m_push(true), + m_recv_status_line(false), + m_recv_all_headers(false), + m_offset(0), + m_start_offset(0), + m_status_code(-1), + m_error_code(0), + m_content_length(-1), + m_stream(NULL), + m_curl(curl), + m_headers(NULL), + m_is_transfer_state(false), + tpcForwardCreds(tpcForwardCreds) { InstallHandlers(curl); } @@ -59,19 +60,20 @@ public: // Note that we are "borrowing" a reference to the curl handle; // it is not owned / freed by the State object. However, we use it // as if there's only one handle per State. - State (off_t start_offset, Stream &stream, CURL *curl, bool push) : - m_push(push), - m_recv_status_line(false), - m_recv_all_headers(false), - m_offset(0), - m_start_offset(start_offset), - m_status_code(-1), - m_error_code(0), - m_content_length(-1), - m_stream(&stream), - m_curl(curl), - m_headers(NULL), - m_is_transfer_state(true) + State (off_t start_offset, Stream &stream, CURL *curl, bool push, bool tpcForwardCreds) : + m_push(push), + m_recv_status_line(false), + m_recv_all_headers(false), + m_offset(0), + m_start_offset(start_offset), + m_status_code(-1), + m_error_code(0), + m_content_length(-1), + m_stream(&stream), + m_curl(curl), + m_headers(NULL), + m_is_transfer_state(true), + tpcForwardCreds(tpcForwardCreds) { InstallHandlers(curl); } @@ -167,6 +169,7 @@ private: std::string m_resp_protocol; // Response protocol in the HTTP status line. std::string m_error_buf; // Any error associated with a response. bool m_is_transfer_state; // If set to true, this state will be used to perform some transfers + bool tpcForwardCreds = false; // if set to true, the redirection will send user credentials to the redirection host }; }; diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index c589c7f6fb8..b5a243a0e24 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -496,7 +496,7 @@ int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state, } int TPCHandler::GetContentLengthTPCPull(CURL *curl, XrdHttpExtReq &req, uint64_t &contentLength, bool & success, TPCLogRecord &rec) { - State state(curl); + State state(curl,req.tpcForwardCreds); //Don't forget to copy the headers of the client's request before doing the HEAD call. Otherwise, if there is a need for authentication, //it will fail state.CopyHeaders(req); @@ -929,7 +929,7 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) curl_easy_setopt(curl, CURLOPT_URL, resource.c_str()); Stream stream(std::move(fh), 0, 0, m_log); - State state(0, stream, curl, true); + State state(0, stream, curl, true, req.tpcForwardCreds); state.CopyHeaders(req); #ifdef XRD_CHUNK_RESP @@ -1080,7 +1080,7 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) return resp_result; } Stream stream(std::move(fh), streams * m_pipelining_multiplier, streams > 1 ? m_block_size : m_small_block_size, m_log); - State state(0, stream, curl, false); + State state(0, stream, curl, false, req.tpcForwardCreds); state.CopyHeaders(req); #ifdef XRD_CHUNK_RESP From df7620c592e94e635e8e036c1b5df94be6ff2682 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 14 May 2024 08:52:18 -0700 Subject: [PATCH 732/773] [Server] Avoid leaking oken information when tracing file opens. --- src/XrdXrootd/XrdXrootdXeq.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index 83a9341c782..0e2d24897ba 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -1440,7 +1440,15 @@ int XrdXrootdProtocol::do_Open() if (opts & kXR_posc) {*op++ = 'p'; openopts |= SFS_O_POSC;} if (opts & kXR_seqio) {*op++ = 'S'; openopts |= SFS_O_SEQIO;} *op = '\0'; - TRACEP(FS, "open " < Date: Fri, 17 May 2024 12:32:14 -0700 Subject: [PATCH 733/773] [Server] Add method to get sanitized env/cgi string. --- src/XrdOuc/XrdOucEnv.cc | 70 +++++++++++++++++++++++++++++++++++++++++ src/XrdOuc/XrdOucEnv.hh | 6 ++++ 2 files changed, 76 insertions(+) diff --git a/src/XrdOuc/XrdOucEnv.cc b/src/XrdOuc/XrdOucEnv.cc index 7d1ec8f71ca..b8b65d9bcd1 100644 --- a/src/XrdOuc/XrdOucEnv.cc +++ b/src/XrdOuc/XrdOucEnv.cc @@ -33,6 +33,7 @@ #include #include "XrdOuc/XrdOucEnv.hh" +#include "XrdOuc/XrdOucString.hh" /******************************************************************************/ /* C o n s t r u c t o r */ @@ -92,7 +93,76 @@ char *XrdOucEnv::Delimit(char *value) else value++; return (char *)0; } + +/******************************************************************************/ +/* Private: E n v B u i l d T i d y */ +/******************************************************************************/ + +#define TIDY_ENVVAR " Xrd Ouc Env " + +void XrdOucEnv::EnvBuildTidy() +{ + char *tidyEnv, *authInfo; + int aBeg, aEnd; + +// We need to sanitize the current env string by removing auth info. If there +// is no auth informationn, then we can short cicuit this. +// + if ((authInfo = strstr(global_env, "authz=")) == 0) + {Put(TIDY_ENVVAR, ""); + return; + } + +// Get position of the auth string and check if we can do a fast deletion. +// Otherwise, we must trudge along. +// + aBeg = authInfo - global_env; + if (aBeg && global_env[aBeg-1] == '&') aBeg--; + if (!(tidyEnv = index(authInfo+6, '&'))) + {char aSave = global_env[aBeg]; + global_env[aBeg] = 0; + Put(TIDY_ENVVAR, global_env); + global_env[aBeg] = aSave; + } else { + XrdOucString tidyStr(global_env); + do{if ((aEnd = tidyStr.find('&', aBeg+6)) == STR_NPOS) + {tidyStr.erase(aBeg); + break; + } + tidyStr.erase(aBeg, aEnd-aBeg); + } while((aBeg = tidyStr.find("&authz=")) != STR_NPOS); + Put(TIDY_ENVVAR, tidyStr.c_str()); + } +} +/******************************************************************************/ +/* E n v T i d y */ +/******************************************************************************/ + +char *XrdOucEnv::EnvTidy(int &envlen) +{ + char *tidyEnv; + int tries = 1; + +// Check if there is no env +// + if ((envlen = global_len) == 0 || global_env == 0) return 0; + +// Check if we have produced a tidy version. If noo build one and try again. +// +do{if ((tidyEnv = Get(TIDY_ENVVAR))) + {if (*tidyEnv == 0) break; + envlen = strlen(tidyEnv); + return tidyEnv; + } + EnvBuildTidy(); + } while(tries--); + +// Return standard env +// + return Env(envlen); +} + /******************************************************************************/ /* E x p o r t */ /******************************************************************************/ diff --git a/src/XrdOuc/XrdOucEnv.hh b/src/XrdOuc/XrdOucEnv.hh index 13dbba88d72..317e71f5d6f 100644 --- a/src/XrdOuc/XrdOucEnv.hh +++ b/src/XrdOuc/XrdOucEnv.hh @@ -47,6 +47,11 @@ public: // inline char *Env(int &envlen) {envlen = global_len; return global_env;} +// EnvTidy() returns the environment string and length with authorization +// information (ie. authz), if any, removed. +// + char *EnvTidy(int &envlen); + // Export() sets an external environmental variable to the desired value // using dynamically allocated fixed storage. // @@ -110,6 +115,7 @@ inline const XrdSecEntity *secEnv() const {return secEntity;} ~XrdOucEnv() {if (global_env) free((void *)global_env);} private: +void EnvBuildTidy(); XrdOucHash env_Hash; const XrdSecEntity *secEntity; From 2d3769835daf9e3d8ba3fe442b5e39b057781be6 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Fri, 17 May 2024 12:37:13 -0700 Subject: [PATCH 734/773] [cmsd] Pass sanitized CGI to cmsd server. Requires b849847 fixes #2247 --- src/XrdCms/XrdCmsFinder.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/XrdCms/XrdCmsFinder.cc b/src/XrdCms/XrdCmsFinder.cc index c9b41bd59dc..8be1d31076b 100644 --- a/src/XrdCms/XrdCmsFinder.cc +++ b/src/XrdCms/XrdCmsFinder.cc @@ -244,8 +244,8 @@ int XrdCmsFinderRMT::Forward(XrdOucErrInfo &Resp, const char *cmd, Data.Path = (char *)arg1; Data.Mode = (char *)arg2; Data.Path2 = (char *)arg2; - Data.Opaque = (Env1 ? Env1->Env(opQ1Len) : 0); - Data.Opaque2 = (Env2 ? Env2->Env(opQ2Len) : 0); + Data.Opaque = (Env1 ? Env1->EnvTidy(opQ1Len) : 0); + Data.Opaque2 = (Env2 ? Env2->EnvTidy(opQ2Len) : 0); // Pack the arguments // @@ -346,7 +346,7 @@ int XrdCmsFinderRMT::Locate(XrdOucErrInfo &Resp, const char *path, int flags, // Data.Ident = (char *)(XrdCmsClientMan::doDebug ? Resp.getErrUser() : ""); Data.Path = (char *)path; - Data.Opaque = (Env ? Env->Env(n) : 0); + Data.Opaque = (Env ? Env->EnvTidy(n) : 0); Data.Avoid = (Env ? Env->Get("tried") : 0); // Set options and command From 552a684aa0f2efca0cfbb6e023af0753b255b657 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 21 May 2024 16:15:48 -0700 Subject: [PATCH 735/773] [HttpTPC] Make sure we sleep the full amount needed. Fixes #2274 --- src/XrdTpc/XrdTpcTPC.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index b5a243a0e24..14b2b9f9051 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include // Delete later!!! #include "XrdTpcState.hh" @@ -439,7 +440,7 @@ int TPCHandler::OpenWaitStall(XrdSfsFile &fh, const std::string &resource, if ((open_result == SFS_STALL) || (open_result == SFS_STARTED)) { int secs_to_stall = fh.error.getErrInfo(); if (open_result == SFS_STARTED) {secs_to_stall = secs_to_stall/2 + 5;} - sleep(secs_to_stall); + std::this_thread::sleep_for (std::chrono::seconds(secs_to_stall)); } break; } From f6b361d4cc9455814d501c074bd76b39cf0eccbe Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 22 May 2024 23:38:55 -0700 Subject: [PATCH 736/773] [HTTP] Always create directory path when opening dest file for HTTP TPC. --- src/XrdTpc/XrdTpcTPC.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 14b2b9f9051..240e180fbe1 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -1061,7 +1061,8 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) } } #endif - int open_result = OpenWaitStall(*fh, full_url, mode|SFS_O_WRONLY, 0644, + int open_result = OpenWaitStall(*fh, full_url, mode|SFS_O_WRONLY, + 0644 | SFS_O_MKPTH, req.GetSecEntity(), authz); if (SFS_REDIRECT == open_result) { int result = RedirectTransfer(curl, redirect_resource, req, fh->error, rec); From 85a37dd6cdaae77242b8242b844f850bb803e8bc Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 21 May 2024 11:05:52 +0200 Subject: [PATCH 737/773] Revert "[XrdHttp] Improved first-line and header parsing" This reverts commit 7514bcb4dbf9c83110c1c28b8f531f32ebf8e6a7. --- src/XrdHttp/XrdHttpProtocol.cc | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 0004cdee23d..4c914467f8d 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -617,19 +617,6 @@ int XrdHttpProtocol::Process(XrdLink *lp) // We ignore the argument here TRACE(DEBUG, " rc:" << rc << " got hdr line: " << tmpline.c_str()); if ((rc == 2) && (tmpline.length() > 1) && (tmpline[rc - 1] == '\n')) { - // A \r\n was detected - if(BuffUsed()) { - if(CurrentReq.request == CurrentReq.rtUnset){ - // The first line has a "\n" at the beginning - // Bad request - return SendSimpleResp(400, nullptr, nullptr, "Got first line break before the HTTP verb", 0, false); - } else { - // We are getting the headers from the buffer and we detected the header end. - // Though, there is still data to read from the buffer, continue to read! - continue; - } - } - // We have detected the header end and no data remains in the buffer, we stop this loop CurrentReq.headerok = true; TRACE(DEBUG, " rc:" << rc << " detected header end."); break; From 3d63b5dd23bb61682661b247f53ec4d6f43369b4 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 22 May 2024 15:01:05 +0200 Subject: [PATCH 738/773] [XrdHttp] Return a 400 bad request if header line is not \r\n terminated Fixes #2273 and #1964 --- src/XrdHttp/XrdHttpProtocol.cc | 9 +++++++-- src/XrdHttp/XrdHttpReq.cc | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 4c914467f8d..20b358ed36b 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -630,9 +630,14 @@ int XrdHttpProtocol::Process(XrdLink *lp) // We ignore the argument here TRACE(DEBUG, " Parsing of first line failed with " << result); return -1; } + } else { + int result = CurrentReq.parseLine((char *) tmpline.c_str(), rc); + if(result < 0) { + TRACE(DEBUG, " Parsing of header line failed with " << result) + SendSimpleResp(400,NULL,NULL,"Malformed header line. Hint: ensure the line finishes with \"\\r\\n\"", 0, false); + return -1; + } } - else - CurrentReq.parseLine((char *)tmpline.c_str(), rc); } diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index e4498537ca4..aa01fa23a7a 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -125,7 +125,7 @@ int XrdHttpReq::parseLine(char *line, int len) { if (!p) { request = rtMalformed; - return 0; + return -1; } pos = (p - line); @@ -145,6 +145,10 @@ int XrdHttpReq::parseLine(char *line, int len) { // We memorize the headers also as a string // because external plugins may need to process it differently std::string ss = val; + if(ss.length() >= 2 && ss.substr(ss.length() - 2, 2) != "\r\n") { + request = rtMalformed; + return -3; + } trim(ss); allheaders[key] = ss; From 42181d90c4878c49de0a6c66a5bd53b65c7f767a Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Wed, 15 May 2024 09:13:09 +0200 Subject: [PATCH 739/773] Fix non-monotonic clock in the throttle After plotting the aggregate I/O throttle time in Prometheus, we were surprised to see the graph was non-monotonic (and didn't match the read time when we put in some fixed sleep times)! This commit fixes two bugs: - The wrong clock was used; this used CPU-time, not wall time. - Incorrect algorithm for normalizing the total time so the nanosecond field didn't represent more than one second. --- src/XrdThrottle/XrdThrottleManager.cc | 5 ++--- src/XrdThrottle/XrdThrottleManager.hh | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/XrdThrottle/XrdThrottleManager.cc b/src/XrdThrottle/XrdThrottleManager.cc index 651bd6d9baf..7c478aef027 100644 --- a/src/XrdThrottle/XrdThrottleManager.cc +++ b/src/XrdThrottle/XrdThrottleManager.cc @@ -19,8 +19,7 @@ const int XrdThrottleManager::m_max_users = 1024; #if defined(__linux__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) -int clock_id; -int XrdThrottleTimer::clock_id = clock_getcpuclockid(0, &clock_id) != ENOENT ? CLOCK_THREAD_CPUTIME_ID : CLOCK_MONOTONIC; +clockid_t XrdThrottleTimer::clock_id = CLOCK_MONOTONIC; #else int XrdThrottleTimer::clock_id = 0; #endif @@ -441,7 +440,7 @@ XrdThrottleManager::RecomputeInternal() while (m_stable_io_wait.tv_nsec > 1000000000) { m_stable_io_wait.tv_nsec -= 1000000000; - m_stable_io_wait.tv_nsec --; + m_stable_io_wait.tv_sec ++; } struct timespec io_wait_ts; io_wait_ts.tv_sec = m_stable_io_wait.tv_sec; diff --git a/src/XrdThrottle/XrdThrottleManager.hh b/src/XrdThrottle/XrdThrottleManager.hh index 6f292ac7c82..53838758a8b 100644 --- a/src/XrdThrottle/XrdThrottleManager.hh +++ b/src/XrdThrottle/XrdThrottleManager.hh @@ -219,7 +219,7 @@ private: XrdThrottleManager &m_manager; struct timespec m_timer; -static int clock_id; +static clockid_t clock_id; }; #endif From 07fc111d8dc4d4c07f6d39d585fff83aaf456b57 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Wed, 15 May 2024 09:17:10 +0200 Subject: [PATCH 740/773] Enable throttle manager timing on Mac OS X With the change to a POSIX-specified clock, all the needed functions are available in OS X. --- src/XrdThrottle/XrdThrottleManager.cc | 2 +- src/XrdThrottle/XrdThrottleManager.hh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/XrdThrottle/XrdThrottleManager.cc b/src/XrdThrottle/XrdThrottleManager.cc index 7c478aef027..d1daec7cc09 100644 --- a/src/XrdThrottle/XrdThrottleManager.cc +++ b/src/XrdThrottle/XrdThrottleManager.cc @@ -18,7 +18,7 @@ XrdThrottleManager::TraceID = "ThrottleManager"; const int XrdThrottleManager::m_max_users = 1024; -#if defined(__linux__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) +#if defined(__linux__) || defined(__APPLE__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) clockid_t XrdThrottleTimer::clock_id = CLOCK_MONOTONIC; #else int XrdThrottleTimer::clock_id = 0; diff --git a/src/XrdThrottle/XrdThrottleManager.hh b/src/XrdThrottle/XrdThrottleManager.hh index 53838758a8b..8d43a4123de 100644 --- a/src/XrdThrottle/XrdThrottleManager.hh +++ b/src/XrdThrottle/XrdThrottleManager.hh @@ -167,7 +167,7 @@ public: void StopTimer() { struct timespec end_timer = {0, 0}; -#if defined(__linux__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) +#if defined(__linux__) || defined(__APPLE__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) int retval = clock_gettime(clock_id, &end_timer); #else int retval = -1; @@ -203,7 +203,7 @@ protected: XrdThrottleTimer(XrdThrottleManager & manager) : m_manager(manager) { -#if defined(__linux__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) +#if defined(__linux__) || defined(__APPLE__) || defined(__GNU__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) int retval = clock_gettime(clock_id, &m_timer); #else int retval = -1; From b91c4e4a97dee6e4da87122c6efa71b9a77825a7 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 6 May 2024 17:30:40 +0200 Subject: [PATCH 741/773] [XrdTpcTPC] Improved curl error reporting to the client All the errors returned to the client now start with "failure:". If the failure is due to a curl error, the error message will contain "HTTP library failure=X" where X is the error reported by libcurl. In addition, all failures indicate the local and the remote resources involved in the TPC transfer --- src/XrdTpc/XrdTpcMultistream.cc | 37 +++++++--- src/XrdTpc/XrdTpcTPC.cc | 126 ++++++++++++++++++-------------- src/XrdTpc/XrdTpcTPC.hh | 1 + 3 files changed, 99 insertions(+), 65 deletions(-) diff --git a/src/XrdTpc/XrdTpcMultistream.cc b/src/XrdTpc/XrdTpcMultistream.cc index 0090b0ae30f..290688f84a3 100644 --- a/src/XrdTpc/XrdTpcMultistream.cc +++ b/src/XrdTpc/XrdTpcMultistream.cc @@ -374,8 +374,10 @@ int TPCHandler::RunCurlWithStreamsImpl(XrdHttpExtReq &req, State &state, } } while (msg); if (res != static_cast(-1) && res != CURLE_OK) { + std::stringstream ss; + ss << "Breaking loop due to failed curl transfer: " << curl_easy_strerror(res); logTransferEvent(LogMask::Debug, rec, "MULTISTREAM_CURL_FAILURE", - "Breaking loop due to failed curl transfer"); + ss.str()); break; } @@ -460,7 +462,7 @@ int TPCHandler::RunCurlWithStreamsImpl(XrdHttpExtReq &req, State &state, ss2 << "; error message: \"" << err << "\""; } logTransferEvent(LogMask::Error, rec, "MULTISTREAM_FAIL", ss.str()); - ss << "failure: " << ss2.str(); + ss << generateClientErr(ss2, rec); } else if (mch.GetErrorCode()) { std::string err = mch.GetErrorMessage(); if (err.empty()) {err = "(no error message provided)";} @@ -468,21 +470,27 @@ int TPCHandler::RunCurlWithStreamsImpl(XrdHttpExtReq &req, State &state, std::stringstream ss2; ss2 << "Error when interacting with local filesystem: " << err; logTransferEvent(LogMask::Error, rec, "MULTISTREAM_FAIL", ss2.str()); - ss << "failure: " << ss2.str(); + ss << generateClientErr(ss2, rec); } else if (res != CURLE_OK) { std::stringstream ss2; - ss2 << "Request failed when processing: " << curl_easy_strerror(res); - logTransferEvent(LogMask::Error, rec, "MULTISTREAM_FAIL", ss.str()); - ss << "failure: " << curl_easy_strerror(res); + ss2 << "Request failed when processing"; + std::stringstream ss3; + ss3 << ss2.str() << ":" << curl_easy_strerror(res); + logTransferEvent(LogMask::Error, rec, "MULTISTREAM_FAIL", ss3.str()); + ss << generateClientErr(ss2, rec, res); } else if (current_offset != content_size) { - ss << "failure: Internal logic error led to early abort; current offset is " << + std::stringstream ss2; + ss2 << "Internal logic error led to early abort; current offset is " << current_offset << " while full size is " << content_size; - logTransferEvent(LogMask::Error, rec, "MULTISTREAM_FAIL", ss.str()); + logTransferEvent(LogMask::Error, rec, "MULTISTREAM_FAIL", ss2.str()); + ss << generateClientErr(ss2, rec); } else { if (!handles[0]->Finalize()) { - ss << "failure: Failed to finalize and close file handle."; + std::stringstream ss2; + ss2 << "Failed to finalize and close file handle."; + ss << generateClientErr(ss2, rec); logTransferEvent(LogMask::Error, rec, "MULTISTREAM_ERROR", - "Failed to finalize and close file handle"); + ss2.str()); } else { ss << "success: Created"; success = true; @@ -506,6 +514,7 @@ int TPCHandler::RunCurlWithStreams(XrdHttpExtReq &req, State &state, { std::vector curl_handles; std::vector handles; + std::stringstream err_ss; try { int retval = RunCurlWithStreamsImpl(req, state, streams, handles, curl_handles, rec); for (std::vector::iterator state_iter = handles.begin(); @@ -523,6 +532,9 @@ int TPCHandler::RunCurlWithStreams(XrdHttpExtReq &req, State &state, rec.status = 500; logTransferEvent(LogMask::Error, rec, "MULTISTREAM_ERROR", e.what()); + std::stringstream ss; + ss << e.what(); + err_ss << generateClientErr(ss, rec); return req.SendSimpleResp(rec.status, NULL, NULL, e.what(), 0); } catch (std::runtime_error &e) { for (std::vector::iterator state_iter = handles.begin(); @@ -533,9 +545,10 @@ int TPCHandler::RunCurlWithStreams(XrdHttpExtReq &req, State &state, logTransferEvent(LogMask::Error, rec, "MULTISTREAM_ERROR", e.what()); std::stringstream ss; - ss << "failure: " << e.what(); + ss << e.what(); + err_ss << generateClientErr(ss, rec); int retval; - if ((retval = req.ChunkResp(ss.str().c_str(), 0))) { + if ((retval = req.ChunkResp(err_ss.str().c_str(), 0))) { return retval; } return req.ChunkResp(NULL, 0); diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 240e180fbe1..72f011a08be 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -381,9 +381,10 @@ int TPCHandler::RedirectTransfer(CURL *curl, const std::string &redirect_resourc const char *ptr = error.getErrText(port); if ((ptr == NULL) || (*ptr == '\0') || (port == 0)) { rec.status = 500; - char msg[] = "Internal error: redirect without hostname"; - logTransferEvent(LogMask::Error, rec, "REDIRECT_INTERNAL_ERROR", msg); - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + std::stringstream ss; + ss << "Internal error: redirect without hostname"; + logTransferEvent(LogMask::Error, rec, "REDIRECT_INTERNAL_ERROR", ss.str()); + return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); } // Construct redirection URL taking into consideration any opaque info @@ -470,23 +471,26 @@ int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state, curl_easy_setopt(curl, CURLOPT_NOBODY, 0); if (res == CURLE_HTTP_RETURNED_ERROR) { std::stringstream ss; - ss << "Remote server failed request: " << curl_easy_strerror(res); + ss << "Remote server failed request"; + std::stringstream ss2; + ss2 << ss.str() << ": " << curl_easy_strerror(res); rec.status = 500; - logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss.str()); - return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, const_cast(curl_easy_strerror(res)), 0) : -1; + logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss2.str()); + return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec, res).c_str(), 0) : -1; } else if (state.GetStatusCode() >= 400) { std::stringstream ss; - ss << "Remote side failed with status code " << state.GetStatusCode(); + ss << "Remote side " << req.clienthost << " failed with status code " << state.GetStatusCode(); rec.status = 500; logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss.str()); - return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, const_cast(ss.str().c_str()), 0): -1; + return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0) : -1; } else if (res) { std::stringstream ss; - ss << "HTTP library failed: " << curl_easy_strerror(res); + ss << "Internal transfer failure"; + std::stringstream ss2; + ss2 << ss.str() << " - HTTP library failed: " << curl_easy_strerror(res); rec.status = 500; - logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss.str()); - char msg[] = "Unknown internal transfer failure"; - return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, msg, 0) : -1; + logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss2.str()); + return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec, res).c_str(), 0) : -1; } std::stringstream ss; ss << "Successfully determined remote size for pull request: " @@ -598,8 +602,9 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, rec.status = 500; logTransferEvent(LogMask::Error, rec, "CURL_INIT_FAIL", "Failed to initialize a libcurl multi-handle"); - char msg[] = "Failed to initialize internal server memory"; - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + std::stringstream ss; + ss << "Failed to initialize internal server memory"; + return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); } //curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 128*1024); @@ -609,11 +614,10 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, if (mres) { rec.status = 500; std::stringstream ss; - ss << "Failed to add transfer to libcurl multi-handle: " << curl_multi_strerror(mres); + ss << "Failed to add transfer to libcurl multi-handle: HTTP library failure=" << curl_multi_strerror(mres); logTransferEvent(LogMask::Error, rec, "CURL_INIT_FAIL", ss.str()); - char msg[] = "Failed to initialize internal server handle"; curl_multi_cleanup(multi_handle); - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); } // Start response to client prior to the first call to curl_multi_perform @@ -715,14 +719,13 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, if (mres != CURLM_OK) { std::stringstream ss; - ss << "Internal libcurl multi-handle error: " << curl_multi_strerror(mres); + ss << "Internal libcurl multi-handle error: HTTP library failure=" << curl_multi_strerror(mres); logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", ss.str()); - char msg[] = "Internal server error due to libcurl"; curl_multi_remove_handle(multi_handle, curl); curl_multi_cleanup(multi_handle); - if ((retval = req.ChunkResp(msg, 0))) { + if ((retval = req.ChunkResp(generateClientErr(ss, rec).c_str(), 0))) { logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL", "Failed to send error message to the TPC client"); return retval; @@ -745,10 +748,11 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, if (!state.GetErrorCode() && res == static_cast(-1)) { // No transfers returned?!? curl_multi_remove_handle(multi_handle, curl); curl_multi_cleanup(multi_handle); - char msg[] = "Internal state error in libcurl"; - logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", msg); + std::stringstream ss; + ss << "Internal state error in libcurl"; + logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", ss.str()); - if ((retval = req.ChunkResp(msg, 0))) { + if ((retval = req.ChunkResp(generateClientErr(ss, rec).c_str(), 0))) { logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL", "Failed to send error message to the TPC client"); return retval; @@ -780,7 +784,7 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, ss2 << "; error message: \"" << err << "\""; } logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str()); - ss << "failure: " << ss2.str(); + ss << generateClientErr(ss2, rec); } else if (state.GetErrorCode()) { std::string err = state.GetErrorMessage(); if (err.empty()) {err = "(no error message provided)";} @@ -788,12 +792,14 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, std::stringstream ss2; ss2 << "Error when interacting with local filesystem: " << err; logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str()); - ss << "failure: " << ss2.str(); + ss << generateClientErr(ss2, rec); } else if (res != CURLE_OK) { std::stringstream ss2; - ss2 << "HTTP library failure: " << curl_easy_strerror(res); - logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str()); - ss << "failure: " << curl_easy_strerror(res); + ss2 << "Internal transfer failure"; + std::stringstream ss3; + ss3 << ss2.str() << ": " << curl_easy_strerror(res); + logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss3.str()); + ss << generateClientErr(ss2, rec, res); } else { ss << "success: Created"; success = true; @@ -871,10 +877,11 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) ManagedCurlHandle curlPtr(curl_easy_init()); auto curl = curlPtr.get(); if (!curl) { - char msg[] = "Failed to initialize internal transfer resources"; + std::stringstream ss; + ss << "Failed to initialize internal transfer resources"; rec.status = 500; - logTransferEvent(LogMask::Error, rec, "PUSH_FAIL", msg); - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + logTransferEvent(LogMask::Error, rec, "PUSH_FAIL", ss.str()); + return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); } curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1); @@ -898,10 +905,11 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) std::unique_ptr fh(m_sfs->newFile(name, file_monid)); if (!fh.get()) { rec.status = 500; + std::stringstream ss; + ss << "Failed to initialize internal transfer file handle"; logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", - "Failed to initialize internal transfer file handle"); - char msg[] = "Failed to initialize internal transfer file handle"; - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + ss.str()); + return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); } std::string full_url = prepareURL(req); @@ -914,15 +922,15 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) return result; } else if (SFS_OK != open_results) { int code; - char msg_generic[] = "Failed to open local resource"; + std::stringstream ss; const char *msg = fh->error.getErrText(code); - if (msg == NULL) msg = msg_generic; + if (msg == NULL) ss << "Failed to open local resource"; + else ss << msg; rec.status = 400; if (code == EACCES) rec.status = 401; else if (code == EEXIST) rec.status = 412; logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", msg); - int resp_result = req.SendSimpleResp(rec.status, NULL, NULL, - const_cast(msg), 0); + int resp_result = req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); fh->close(); return resp_result; } @@ -958,10 +966,11 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) ManagedCurlHandle curlPtr(curl_easy_init()); auto curl = curlPtr.get(); if (!curl) { - char msg[] = "Failed to initialize internal transfer resources"; - rec.status = 500; - logTransferEvent(LogMask::Error, rec, "PULL_FAIL", msg); - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + std::stringstream ss; + ss << "Failed to initialize internal transfer resources"; + rec.status = 500; + logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str()); + return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); } // ddavila 2023-01-05: // The following change was required by the Rucio/SENSE project where @@ -1007,10 +1016,11 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT); std::unique_ptr fh(m_sfs->newFile(name, m_monid++)); if (!fh.get()) { - char msg[] = "Failed to initialize internal transfer file handle"; - rec.status = 500; - logTransferEvent(LogMask::Error, rec, "PULL_FAIL", msg); - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + std::stringstream ss; + ss << "Failed to initialize internal transfer file handle"; + rec.status = 500; + logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str()); + return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); } auto query_header = req.headers.find("xrd-http-fullresource"); std::string redirect_resource = req.resource; @@ -1032,10 +1042,11 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) } catch (...) { // Handled below } if (stream_req < 0 || stream_req > 100) { - char msg[] = "Invalid request for number of streams"; + std::stringstream ss; + ss << "Invalid request for number of streams"; rec.status = 400; - logTransferEvent(LogMask::Info, rec, "INVALID_REQUEST", msg); - return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); + logTransferEvent(LogMask::Info, rec, "INVALID_REQUEST", ss.str()); + return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0); } streams = stream_req == 0 ? 1 : stream_req; } @@ -1069,15 +1080,16 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) return result; } else if (SFS_OK != open_result) { int code; - char msg_generic[] = "Failed to open local resource"; + std::stringstream ss; const char *msg = fh->error.getErrText(code); - if ((msg == NULL) || (*msg == '\0')) msg = msg_generic; + if ((msg == NULL) || (*msg == '\0')) ss << "Failed to open local resource"; + else ss << msg; rec.status = 400; if (code == EACCES) rec.status = 401; else if (code == EEXIST) rec.status = 412; - logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", msg); + logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", ss.str()); int resp_result = req.SendSimpleResp(rec.status, NULL, NULL, - const_cast(msg), 0); + generateClientErr(ss, rec).c_str(), 0); fh->close(); return resp_result; } @@ -1124,6 +1136,14 @@ void TPCHandler::logTransferEvent(LogMask mask, const TPCLogRecord &rec, m_log.Log(mask, rec.log_prefix.c_str(), ss.str().c_str()); } +std::string TPCHandler::generateClientErr(std::stringstream &err_ss, const TPCLogRecord &rec, CURLcode cCode) { + std::stringstream ssret; + ssret << "failure: " << err_ss.str() << ", local=" << rec.local <<", remote=" << rec.remote; + if(cCode != CURLcode::CURLE_OK) { + ssret << ", HTTP library failure=" << curl_easy_strerror(cCode); + } + return ssret.str(); +} /******************************************************************************/ /* X r d H t t p G e t E x t H a n d l e r */ /******************************************************************************/ diff --git a/src/XrdTpc/XrdTpcTPC.hh b/src/XrdTpc/XrdTpcTPC.hh index 7c4515d5317..1c1e9cffc10 100644 --- a/src/XrdTpc/XrdTpcTPC.hh +++ b/src/XrdTpc/XrdTpcTPC.hh @@ -142,6 +142,7 @@ private: void logTransferEvent(LogMask lvl, const TPCLogRecord &record, const std::string &event, const std::string &message=""); + std::string generateClientErr(std::stringstream &err_ss, const TPCLogRecord &rec, CURLcode cCode = CURLcode::CURLE_OK); static int m_marker_period; static size_t m_block_size; static size_t m_small_block_size; From 1e65a4a34fdf792fb2b35c8aaf67689514e0bcfb Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 28 May 2024 15:27:11 -0700 Subject: [PATCH 742/773] [OSS] Add feature setting for Extended ERror Text is available. --- src/XrdOss/XrdOss.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdOss/XrdOss.hh b/src/XrdOss/XrdOss.hh index e1892ade9b5..8207dcc1391 100644 --- a/src/XrdOss/XrdOss.hh +++ b/src/XrdOss/XrdOss.hh @@ -479,6 +479,7 @@ short rsvd; // Reserved #define XRDOSS_HASCACH 0x0000000000000010ULL #define XRDOSS_HASNAIO 0x0000000000000020ULL #define XRDOSS_HASRPXY 0x0000000000000040ULL +#define XRDOSS_HASXERT 0x0000000000000080ULL // Options that can be passed to Stat() // From 4c184bc8b97a3079f49a41ce9f9f5bf2d853b963 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 6 Jun 2024 17:42:34 +0200 Subject: [PATCH 743/773] XrdHttp: obfuscate token authz leaking to the logs Fixes #2222 --- src/XrdHttp/XrdHttpProtocol.cc | 10 ++++-- src/XrdHttp/XrdHttpReq.cc | 10 ++++-- src/XrdOuc/XrdOucUtils.cc | 44 +++++++++++++++++++++++++++ src/XrdOuc/XrdOucUtils.hh | 5 +++ tests/CMakeLists.txt | 2 ++ tests/XrdOucTests/CMakeLists.txt | 6 ++++ tests/XrdOucTests/XrdOucUtilsTests.cc | 27 ++++++++++++++++ 7 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 tests/XrdOucTests/CMakeLists.txt create mode 100644 tests/XrdOucTests/XrdOucUtilsTests.cc diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 20b358ed36b..c66f628dde0 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -42,6 +42,7 @@ #include "XrdTls/XrdTls.hh" #include "XrdTls/XrdTlsContext.hh" +#include "XrdOuc/XrdOucUtils.hh" #include #include @@ -51,6 +52,7 @@ #include #include #include +#include #define XRHTTP_TK_GRACETIME 600 @@ -312,7 +314,6 @@ char *XrdHttpProtocol::GetClientIPStr() { return strdup(buf); } - // Various routines for handling XrdLink as BIO objects within OpenSSL. #if OPENSSL_VERSION_NUMBER < 0x1000105fL int BIO_XrdLink_write(BIO *bio, const char *data, size_t datal, size_t *written) @@ -614,8 +615,11 @@ int XrdHttpProtocol::Process(XrdLink *lp) // We ignore the argument here // Read as many lines as possible into the buffer. An empty line breaks while ((rc = BuffgetLine(tmpline)) > 0) { - TRACE(DEBUG, " rc:" << rc << " got hdr line: " << tmpline.c_str()); - + if (TRACING(TRACE_DEBUG)) { + std::string traceLine{tmpline.c_str()}; + traceLine = XrdOucUtils::obfuscate(traceLine, {"authorization", "transferheaderauthorization"}, ':', '\n'); + TRACE(DEBUG, " rc:" << rc << " got hdr line: " << traceLine); + } if ((rc == 2) && (tmpline.length() > 1) && (tmpline[rc - 1] == '\n')) { CurrentReq.headerok = true; TRACE(DEBUG, " rc:" << rc << " detected header end."); diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index aa01fa23a7a..50dacc7a870 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -936,9 +936,15 @@ int XrdHttpReq::ProcessHTTPReq() { char *q = quote(hdr2cgistr.c_str()); resourceplusopaque.append(q); - TRACEI(DEBUG, "Appended header fields to opaque info: '" - << hdr2cgistr.c_str() << "'"); + if (TRACING(TRACE_DEBUG)) { + // The obfuscation of "authz" will only be done if the server http.header2cgi config contains something that maps a header to this "authz" cgi. + // Unfortunately the obfuscation code will be called no matter what is configured in http.header2cgi. + std::string header2cgistrObf = XrdOucUtils::obfuscate(hdr2cgistr, {"authz"}, '=', '&'); + TRACEI(DEBUG, "Appended header fields to opaque info: '" + << header2cgistrObf.c_str() << "'"); + + } // We assume that anything appended to the CGI str should also // apply to the destination in case of a MOVE. if (strchr(destination.c_str(), '?')) destination.append("&"); diff --git a/src/XrdOuc/XrdOucUtils.cc b/src/XrdOuc/XrdOucUtils.cc index bac187722d2..ef06f4b4646 100644 --- a/src/XrdOuc/XrdOucUtils.cc +++ b/src/XrdOuc/XrdOucUtils.cc @@ -33,6 +33,8 @@ #include #include #include +#include +#include #ifdef WIN32 #include @@ -1409,5 +1411,47 @@ void XrdOucUtils::trim(std::string &str) { while( str.size() && !isgraph(str[str.size()-1]) ) str.resize (str.size () - 1); } + +std::string XrdOucUtils::obfuscate(const std::string & input, const std::unordered_set & keysToObfuscate,const char keyValueDelimiter, const char listDelimiter) { + // String stream to build the result + std::ostringstream result; + + // Start processing the input string + std::istringstream inputStream(input); + std::string pair; + bool first = true; + + // Split input by listDelimiter + while (std::getline(inputStream, pair, listDelimiter)) { + if (!first) { + result << listDelimiter; + } else { + first = false; + } + + // Find the position of the keyValueDelimiter + size_t delimiterPos = pair.find(keyValueDelimiter); + if (delimiterPos != std::string::npos) { + std::string key = pair.substr(0, delimiterPos); + trim(key); + std::string lowerCasedKey = key; + std::transform(lowerCasedKey.begin(),lowerCasedKey.end(),lowerCasedKey.begin(),::tolower); + std::string value = pair.substr(delimiterPos + 1); + + // Check if the key needs to be obfuscated + if (keysToObfuscate.find(lowerCasedKey) != keysToObfuscate.end()) { + result << key << keyValueDelimiter << OBFUSCATION_STR; + } else { + result << pair; + } + } else { + result << pair; // In case there's no delimiter in the pair, just append it + } + } + + return result.str(); +} + + #endif diff --git a/src/XrdOuc/XrdOucUtils.hh b/src/XrdOuc/XrdOucUtils.hh index 912f0f13855..42989798a18 100644 --- a/src/XrdOuc/XrdOucUtils.hh +++ b/src/XrdOuc/XrdOucUtils.hh @@ -33,6 +33,7 @@ #include #include #include +#include class XrdSysError; class XrdOucString; @@ -42,6 +43,8 @@ class XrdOucUtils { public: +static inline std::string OBFUSCATION_STR = "REDACTED"; + static const mode_t pathMode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; static int argList(char *args, char **argV, int argC); @@ -133,6 +136,8 @@ static int getModificationTime(const char * path, time_t & modificationTime); static void trim(std::string & str); +static std::string obfuscate(const std::string & input, const std::unordered_set & keysToObfuscate,const char keyValueDelimiter, const char listDelimiter); + XrdOucUtils() {} ~XrdOucUtils() {} }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3648cbfdb7e..2b115ba8d9c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,8 @@ add_subdirectory(XrdEc) add_subdirectory(XrdHttpTests) +add_subdirectory(XrdOucTests) + add_subdirectory( XrdSsiTests ) if(NOT ENABLE_SERVER_TESTS) diff --git a/tests/XrdOucTests/CMakeLists.txt b/tests/XrdOucTests/CMakeLists.txt new file mode 100644 index 00000000000..3a72367521e --- /dev/null +++ b/tests/XrdOucTests/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(xrdoucutils-unit-tests XrdOucUtilsTests.cc) + +target_link_libraries(xrdoucutils-unit-tests XrdUtils GTest::GTest GTest::Main) +target_include_directories(xrdoucutils-unit-tests PRIVATE ${CMAKE_SOURCE_DIR}/src) + +gtest_discover_tests(xrdoucutils-unit-tests) diff --git a/tests/XrdOucTests/XrdOucUtilsTests.cc b/tests/XrdOucTests/XrdOucUtilsTests.cc new file mode 100644 index 00000000000..8c44af09f9f --- /dev/null +++ b/tests/XrdOucTests/XrdOucUtilsTests.cc @@ -0,0 +1,27 @@ +#undef NDEBUG + +#include +#include + +#include "XrdOuc/XrdOucUtils.hh" + + +using namespace testing; + +class XrdOucUtilsTests : public Test {}; + +TEST(XrdOucUtilsTests, obfuscate) { + // General cases + ASSERT_EQ(std::string("scitag.flow=144&authz=") + XrdOucUtils::OBFUSCATION_STR + std::string("&test=abcd"), XrdOucUtils::obfuscate("scitag.flow=144&authz=token&test=abcd",{"authz"},'=','&')); + ASSERT_EQ(std::string("authz=") + XrdOucUtils::OBFUSCATION_STR + std::string("&scitag.flow=144&test=abcd"), XrdOucUtils::obfuscate("authz=token&scitag.flow=144&test=abcd",{"authz"},'=','&')); + ASSERT_EQ(std::string("scitag.flow=144&test=abcd&authz=") + XrdOucUtils::OBFUSCATION_STR, XrdOucUtils::obfuscate("scitag.flow=144&test=abcd&authz=token",{"authz"},'=','&')); + // Nothing to obfuscate + ASSERT_EQ("test=abcd&test2=abcde",XrdOucUtils::obfuscate("test=abcd&test2=abcde",{},'=','&')); + ASSERT_EQ("nothingtoobfuscate",XrdOucUtils::obfuscate("nothingtoobfuscate",{"obfuscateme"},'=','\n')); + //Empty string obfuscation + ASSERT_EQ("",XrdOucUtils::obfuscate("",{"obfuscateme"},'=','&')); + ASSERT_EQ("",XrdOucUtils::obfuscate("",{},'=','\n')); + // Trimmed key obfuscation + ASSERT_EQ(std::string("Authorization:") + XrdOucUtils::OBFUSCATION_STR, XrdOucUtils::obfuscate("Authorization: Bearer token",{"authorization"},':','\n')); + ASSERT_EQ(std::string("Authorization:")+ XrdOucUtils::OBFUSCATION_STR, XrdOucUtils::obfuscate("Authorization : Bearer token",{"authorization"},':','\n')); +} \ No newline at end of file From 8af86c8d9ff6ae026b02b5d59db24ecde140ec60 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 19 Jun 2024 13:45:28 +0000 Subject: [PATCH 744/773] static array --- src/XrdCms/XrdCmsCluster.cc | 40 +++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/XrdCms/XrdCmsCluster.cc b/src/XrdCms/XrdCmsCluster.cc index 3ade6d0880e..267baeaadbb 100644 --- a/src/XrdCms/XrdCmsCluster.cc +++ b/src/XrdCms/XrdCmsCluster.cc @@ -1787,10 +1787,17 @@ 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; + bool useWR=(Config.P_randlb==1); + //default 0 to skip the node in random selection if the below checks fail + int Weighed[STMax] = { 0 }; + //float scalingFactor = 1+(1-float(std::clamp(Config.P_fuzz,0,100))/100)*2; + for (int i = 0; i <= STHi; i++) + // Weighed[i] = 0; if ((np = NodeTab[i]) && (np->NodeMask & mask)) {if (!(selR.needNet & np->hasNet)) {selR.xNoNet= true; continue;} selR.nPick++; @@ -1801,7 +1808,14 @@ 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 (useWR){ + //add 1 to the inverse load, this is to allow some selection in case reported loads hit 100 + int nload = 101 - np->myLoad; + selCap += nload;//static_cast(nload + std::pow(nload, scalingFactor)/2); + Weighed[i] = selCap; + } + 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; @@ -1817,8 +1831,22 @@ XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) } Multi = true; } + } } - + if (useWR){ + // pick a random weighed node + // + static std::random_device rand_dev; + static std::default_random_engine generator(rand_dev()); + static 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; + } + } + } // Check for overloaded node and return result // if (!sp) return calcDelay(selR); From d81af684d3acbc2aa6def174bfc1385523059c90 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 19 Jun 2024 14:04:55 +0000 Subject: [PATCH 745/773] static array --- src/XrdCms/XrdCmsConfig.cc | 4 +++- src/XrdCms/XrdCmsConfig.hh | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/XrdCms/XrdCmsConfig.cc b/src/XrdCms/XrdCmsConfig.cc index 9512df778d6..4fc0ca026a6 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 algorithm 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..89f4c01def0 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 weighted random load balancing char DoMWChk; // When true (default) perform multiple write check char DoHnTry; // When true (default) use hostnames for try redirs From a8181eb958f9336946eea091b522c088db733673 Mon Sep 17 00:00:00 2001 From: Jyothish Thomas Date: Fri, 21 Jun 2024 08:00:53 +0000 Subject: [PATCH 746/773] stop crashes due to CA --- 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 b721079b721..20a9156a733 100644 --- a/src/XrdSecgsi/XrdSecgsitest.cc +++ b/src/XrdSecgsi/XrdSecgsitest.cc @@ -276,6 +276,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())) { @@ -325,7 +327,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 9686073dbb4ee379e68c2edf194e7df0cc8fe3df Mon Sep 17 00:00:00 2001 From: Jyothish Thomas Date: Fri, 21 Jun 2024 11:39:37 +0000 Subject: [PATCH 747/773] pgrwdbg --- packaging/xrootd-5.5.4-12.el8.src.rpm | Bin 0 -> 3216313 bytes src/XrdXrootd/XrdXrootdXeqPgrw.cc | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 packaging/xrootd-5.5.4-12.el8.src.rpm diff --git a/packaging/xrootd-5.5.4-12.el8.src.rpm b/packaging/xrootd-5.5.4-12.el8.src.rpm new file mode 100644 index 0000000000000000000000000000000000000000..f2f4d9b98f3bb980ce165aa13a98665076c66f31 GIT binary patch literal 3216313 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≠1sBtS?ZZvtITF| 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
    ~3E;3`*bkZ!TW>n$`oz&%?rp^1K~b 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

    rn5F$|eN3Xaj^E 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 =02UW4Equ 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�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�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

HLe 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

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